diff --git a/config/WootecoStyle.xml b/config/WootecoStyle.xml
index cac33a8..eccfcb4 100644
--- a/config/WootecoStyle.xml
+++ b/config/WootecoStyle.xml
@@ -1,56 +1,95 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index e69de29..d31ebc8 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -0,0 +1,8 @@
+## 도메인 요구사항
+
+## 상태별 요구사항
+
+- 매칭 초기화 초기상태
+ - [ ] 매칭의 초기상태로 다음 상태는 사용자 입력 선택 상태입니다.
+- 사용장 명령어 선택 상태
+ - [ ] 사용자로부터 입력을 기능선택의 입력을 받아옵니다.
diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java
index c619a21..bf20ad3 100644
--- a/src/main/java/pairmatching/Application.java
+++ b/src/main/java/pairmatching/Application.java
@@ -1,8 +1,10 @@
package pairmatching;
+import pairmatching.launcher.PairmatchingLauncher;
+
public class Application {
public static void main(String[] args) {
// TODO 구현 진행
-
+ new PairmatchingLauncher().execute();
}
}
diff --git a/src/main/java/pairmatching/domain/code/Course.java b/src/main/java/pairmatching/domain/code/Course.java
new file mode 100644
index 0000000..f95d087
--- /dev/null
+++ b/src/main/java/pairmatching/domain/code/Course.java
@@ -0,0 +1,34 @@
+package pairmatching.domain.code;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+public enum Course {
+ BACKEND("백엔드", "backend-crew.md"),
+ FRONTEND("프론트엔드", "frontend-crew.md");
+
+ private final String name;
+ private final String fileName;
+
+ Course(final String name, final String fileName) {
+ this.name = name;
+ this.fileName = fileName;
+ }
+
+ public static String messages() {
+ return Arrays.stream(values())
+ .map(course -> course.name)
+ .collect(Collectors.joining(" | "));
+ }
+
+ public static Course from(String input) {
+ return Arrays.stream(values())
+ .filter(course -> course.name.equals(input.trim()))
+ .findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public String getFileName() {
+ return this.fileName;
+ }
+}
diff --git a/src/main/java/pairmatching/domain/code/Level.java b/src/main/java/pairmatching/domain/code/Level.java
new file mode 100644
index 0000000..2ed4b91
--- /dev/null
+++ b/src/main/java/pairmatching/domain/code/Level.java
@@ -0,0 +1,47 @@
+package pairmatching.domain.code;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public enum Level {
+ LEVEL1("레벨1", List.of(Subject.RACING, Subject.LOTTO, Subject.BASEBALL)),
+ LEVEL2("레벨2", List.of(Subject.SHOPPING, Subject.PAYMENT, Subject.SUBWAY)),
+ LEVEL3("레벨3"),
+ LEVEL4("레벨4", List.of(Subject.IMPROVE_PERFORMANCE, Subject.DEPLOY)), LEVEL5("레벨5");
+
+ private final String name;
+ private final List subjects;
+
+ Level(final String name) {
+ this.name = name;
+ this.subjects = new ArrayList<>();
+ }
+
+ Level(final String name, final List subjects) {
+ this.name = name;
+ this.subjects = subjects;
+ }
+
+ public static Level from(final String name, final Subject subject) {
+ return Arrays.stream(values())
+ .filter(level -> level.name.equals(name.trim()))
+ .filter(level -> level.subjects.contains(subject))
+ .findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public static String messages() {
+ return Arrays.stream(values())
+ .map(Level::message)
+ .collect(Collectors.joining("\n"));
+ }
+
+ private String message() {
+ var subjectNames = subjects.stream()
+ .map(Subject::getName)
+ .collect(Collectors.joining(" | "));
+ return String.format(" - %s: %s", name, subjectNames);
+ }
+}
diff --git a/src/main/java/pairmatching/domain/code/Subject.java b/src/main/java/pairmatching/domain/code/Subject.java
new file mode 100644
index 0000000..fef071a
--- /dev/null
+++ b/src/main/java/pairmatching/domain/code/Subject.java
@@ -0,0 +1,32 @@
+package pairmatching.domain.code;
+
+import java.util.Arrays;
+
+public enum Subject {
+ RACING("자동차경주"),
+ LOTTO("로또"),
+ BASEBALL("숫자야구게임"),
+ SHOPPING("장바구니"),
+ PAYMENT("결제"),
+ SUBWAY("지하철노선도"),
+ IMPROVE_PERFORMANCE("성능개선"),
+ DEPLOY("배포");
+
+
+ private final String name;
+
+ Subject(final String input) {
+ this.name = input;
+ }
+
+ public static Subject from(final String input) {
+ return Arrays.stream(values())
+ .filter(subject -> subject.name.equals(input.trim()))
+ .findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/main/java/pairmatching/domain/count/RetryCount.java b/src/main/java/pairmatching/domain/count/RetryCount.java
new file mode 100644
index 0000000..bad72ee
--- /dev/null
+++ b/src/main/java/pairmatching/domain/count/RetryCount.java
@@ -0,0 +1,24 @@
+package pairmatching.domain.count;
+
+public class RetryCount {
+ private static final int INITIALIZE_COUNT = 0;
+ private static final int PLUS_COUNT = 1;
+ private static final int MAX_COUNT = 3;
+ private final int count;
+
+ private RetryCount(final int count) {
+ if (count > MAX_COUNT) {
+ throw new IllegalStateException("3회이상 매칭을 시도할 수 없습니다.");
+ }
+ this.count = count;
+ }
+
+ public static RetryCount generateInitializeCount() {
+ return new RetryCount(INITIALIZE_COUNT);
+ }
+
+ public RetryCount plusRetry() {
+ return new RetryCount(this.count + PLUS_COUNT);
+ }
+
+}
diff --git a/src/main/java/pairmatching/domain/generator/CrewGenerator.java b/src/main/java/pairmatching/domain/generator/CrewGenerator.java
new file mode 100644
index 0000000..4266469
--- /dev/null
+++ b/src/main/java/pairmatching/domain/generator/CrewGenerator.java
@@ -0,0 +1,48 @@
+package pairmatching.domain.generator;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+import pairmatching.domain.code.Course;
+import pairmatching.domain.matching.Crew;
+
+public class CrewGenerator {
+ private final ReadFile readFile;
+ private final ShuffleGenerator shuffleGenerator;
+
+
+ public CrewGenerator(ReadFile readFile, ShuffleGenerator shuffleGenerator) {
+ this.readFile = readFile;
+ this.shuffleGenerator = shuffleGenerator;
+ }
+
+ public List generateCrews(Course course) {
+ var crewNames = this.readFileByCrews(course.getFileName());
+
+ var generatedCrews = this.shuffleGenerator.shuffle(crewNames)
+ .stream()
+ .map(name -> new Crew(course, name))
+ .collect(Collectors.toList());
+
+ validateCrewSizeIsGreaterThanMinSize(generatedCrews.size());
+ validateDuplicatedCrew(generatedCrews);
+
+ return generatedCrews;
+ }
+
+ private void validateDuplicatedCrew(List generatedCrews) {
+ if (generatedCrews.size() != new HashSet<>(generatedCrews).size()) {
+ throw new IllegalStateException("크루의 이름이 중복됩니다.");
+ }
+ }
+
+ private void validateCrewSizeIsGreaterThanMinSize(int size) {
+ if (size < 2) {
+ throw new IllegalStateException("매칭할 수 없습니다.");
+ }
+ }
+
+ private List readFileByCrews(final String fileName) {
+ return readFile.readFile(fileName);
+ }
+}
diff --git a/src/main/java/pairmatching/domain/generator/ReadFile.java b/src/main/java/pairmatching/domain/generator/ReadFile.java
new file mode 100644
index 0000000..7cca35c
--- /dev/null
+++ b/src/main/java/pairmatching/domain/generator/ReadFile.java
@@ -0,0 +1,7 @@
+package pairmatching.domain.generator;
+
+import java.util.List;
+
+public interface ReadFile {
+ List readFile(String fileName);
+}
diff --git a/src/main/java/pairmatching/domain/generator/ReadFileImpl.java b/src/main/java/pairmatching/domain/generator/ReadFileImpl.java
new file mode 100644
index 0000000..60d6a77
--- /dev/null
+++ b/src/main/java/pairmatching/domain/generator/ReadFileImpl.java
@@ -0,0 +1,23 @@
+package pairmatching.domain.generator;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.List;
+
+public class ReadFileImpl implements ReadFile {
+ private static final String RESOURCES = "src/main/resources/";
+
+ @Override
+ public List readFile(String fileName) {
+ return this.read(fileName);
+ }
+
+ private List read(String fileName) {
+ try {
+ return Files.readAllLines(Paths.get(RESOURCES + fileName));
+ } catch (IOException exception) {
+ throw new IllegalStateException("잘못된 파일이거나 읽을 수 없습니다.");
+ }
+ }
+}
diff --git a/src/main/java/pairmatching/domain/generator/ShuffleGenerator.java b/src/main/java/pairmatching/domain/generator/ShuffleGenerator.java
new file mode 100644
index 0000000..ac9f9f6
--- /dev/null
+++ b/src/main/java/pairmatching/domain/generator/ShuffleGenerator.java
@@ -0,0 +1,8 @@
+package pairmatching.domain.generator;
+
+import java.util.List;
+
+public interface ShuffleGenerator {
+
+ List shuffle(List input);
+}
diff --git a/src/main/java/pairmatching/domain/generator/ShuffleGeneratorImpl.java b/src/main/java/pairmatching/domain/generator/ShuffleGeneratorImpl.java
new file mode 100644
index 0000000..b85822d
--- /dev/null
+++ b/src/main/java/pairmatching/domain/generator/ShuffleGeneratorImpl.java
@@ -0,0 +1,12 @@
+package pairmatching.domain.generator;
+
+import camp.nextstep.edu.missionutils.Randoms;
+import java.util.List;
+
+public class ShuffleGeneratorImpl implements ShuffleGenerator {
+
+ @Override
+ public List shuffle(List input) {
+ return Randoms.shuffle(input);
+ }
+}
diff --git a/src/main/java/pairmatching/domain/matching/Crew.java b/src/main/java/pairmatching/domain/matching/Crew.java
new file mode 100644
index 0000000..54677f0
--- /dev/null
+++ b/src/main/java/pairmatching/domain/matching/Crew.java
@@ -0,0 +1,45 @@
+package pairmatching.domain.matching;
+
+import java.util.Objects;
+import pairmatching.domain.code.Course;
+
+public class Crew {
+ private static final int MIN_SIZE = 1;
+ private final Course course;
+ private final String name;
+
+ public Crew(Course course, String name) {
+ var tempName = name.trim();
+ validateNameSizeIsGreaterThanMinSize(tempName.length());
+ this.course = course;
+ this.name = name;
+ }
+
+ private void validateNameSizeIsGreaterThanMinSize(int length) {
+ if (length < MIN_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Crew crew = (Crew) o;
+ return course == crew.course && Objects.equals(name, crew.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(course, name);
+ }
+
+ public String name() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/pairmatching/domain/matching/MatchedCrew.java b/src/main/java/pairmatching/domain/matching/MatchedCrew.java
new file mode 100644
index 0000000..89a5695
--- /dev/null
+++ b/src/main/java/pairmatching/domain/matching/MatchedCrew.java
@@ -0,0 +1,40 @@
+package pairmatching.domain.matching;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class MatchedCrew {
+ private final Set matchedCrew;
+
+ public MatchedCrew() {
+ this.matchedCrew = new LinkedHashSet<>();
+ }
+
+ public void add(Crew crew) {
+ this.matchedCrew.add(crew);
+ }
+
+ public void addAll(List crews) {
+ this.matchedCrew.addAll(crews);
+ }
+
+ public String result() {
+ return matchedCrew.stream()
+ .map(Crew::name)
+ .collect(Collectors.joining(" : "));
+ }
+
+ public boolean hasMatchedAtBefore(List targetMatchedCrews) {
+ return targetMatchedCrews.stream()
+ .anyMatch(targetMatchedCrew -> targetMatchedCrew.hasMatchedAtBefore(this));
+ }
+
+ private boolean hasMatchedAtBefore(MatchedCrew matchedCrew) {
+ var union = new HashSet<>(matchedCrew.matchedCrew);
+ union.retainAll(this.matchedCrew);
+ return union.size() >= 2;
+ }
+}
diff --git a/src/main/java/pairmatching/domain/matching/MatchedCrews.java b/src/main/java/pairmatching/domain/matching/MatchedCrews.java
new file mode 100644
index 0000000..304d45c
--- /dev/null
+++ b/src/main/java/pairmatching/domain/matching/MatchedCrews.java
@@ -0,0 +1,28 @@
+package pairmatching.domain.matching;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MatchedCrews {
+ private final List matchedCrewList;
+
+ public MatchedCrews() {
+ this.matchedCrewList = new ArrayList<>();
+ }
+
+ public void add(MatchedCrew matchedCrew) {
+ this.matchedCrewList.add(matchedCrew);
+ }
+
+ public String result() {
+ return this.matchedCrewList.stream()
+ .map(MatchedCrew::result)
+ .collect(Collectors.joining("\n"));
+ }
+
+ public boolean hasMatchedAtBefore(MatchedCrews targetMatchedCrews) {
+ return matchedCrewList.stream()
+ .anyMatch(matchedCrew -> matchedCrew.hasMatchedAtBefore(targetMatchedCrews.matchedCrewList));
+ }
+}
diff --git a/src/main/java/pairmatching/domain/matching/MatchingDivision.java b/src/main/java/pairmatching/domain/matching/MatchingDivision.java
new file mode 100644
index 0000000..1f111dc
--- /dev/null
+++ b/src/main/java/pairmatching/domain/matching/MatchingDivision.java
@@ -0,0 +1,54 @@
+package pairmatching.domain.matching;
+
+import java.util.Objects;
+import pairmatching.domain.code.Course;
+import pairmatching.domain.code.Level;
+import pairmatching.domain.code.Subject;
+
+public class MatchingDivision {
+ private static final int FIX_SIZE = 3;
+
+ private final Course course;
+ private final Level level;
+ private final Subject subject;
+
+ public MatchingDivision(String input) {
+ var splitStr = input.split(",");
+ validateSplitSize(splitStr.length);
+
+ this.course = Course.from(splitStr[0]);
+ this.subject = Subject.from(splitStr[2]);
+ this.level = Level.from(splitStr[1], this.subject);
+ }
+
+ private void validateSplitSize(int length) {
+ if (length != FIX_SIZE) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MatchingDivision that = (MatchingDivision) o;
+ return course == that.course && level == that.level && subject == that.subject;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(course, level, subject);
+ }
+
+ public Course getCourse() {
+ return this.course;
+ }
+
+ public boolean isSameLevel(MatchingDivision matchingDivision) {
+ return this.level.equals(matchingDivision.level);
+ }
+}
diff --git a/src/main/java/pairmatching/domain/matching/MatchingResult.java b/src/main/java/pairmatching/domain/matching/MatchingResult.java
new file mode 100644
index 0000000..5d3b003
--- /dev/null
+++ b/src/main/java/pairmatching/domain/matching/MatchingResult.java
@@ -0,0 +1,86 @@
+package pairmatching.domain.matching;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import pairmatching.exception.BeforeMatchedCrewException;
+import pairmatching.exception.DuplicatedMatchingDivisionException;
+import pairmatching.exception.MatchingDivisionDidNotExists;
+
+public class MatchingResult {
+ private final Map matchingResult;
+
+ public MatchingResult() {
+ this.matchingResult = new HashMap<>();
+ }
+
+ public void matchPair(MatchingDivision matchingDivision, List crews) {
+ this.validateDuplicatedKey(matchingDivision);
+
+ var matchedResult = generateMatchedCrews(new LinkedList<>(crews));
+
+ this.validateSameLevelExistsMatchedCrew(matchingDivision, matchedResult);
+ this.matchingResult.put(matchingDivision, matchedResult);
+ }
+
+ public void rematchPair(MatchingDivision matchingDivision, List crews) {
+ var matchedResult = generateMatchedCrews(new LinkedList<>(crews));
+
+ this.validateSameLevelExistsMatchedCrew(matchingDivision, matchedResult);
+ this.matchingResult.put(matchingDivision, matchedResult);
+ }
+
+ /**
+ * 같은 레벨에서 매칭된 크루가 잇는지 검증합니다.
+ *
+ * @param matchedResult
+ */
+ private void validateSameLevelExistsMatchedCrew(MatchingDivision matchingDivision, MatchedCrews matchedResult) {
+
+ var hasSameLevelMatchedCrews = this.matchingResult.entrySet().stream()
+ .filter(entry -> entry.getKey().isSameLevel(matchingDivision))
+ .filter(entry -> !entry.getKey().equals(matchingDivision)) // 등록된 구분값을 제외함
+ .anyMatch(entry -> entry.getValue().hasMatchedAtBefore(matchedResult));
+
+ if (hasSameLevelMatchedCrews) {
+ throw new BeforeMatchedCrewException();
+ }
+ }
+
+ private void validateDuplicatedKey(MatchingDivision matchingDivision) {
+ if (this.matchingResult.containsKey(matchingDivision)) {
+ throw new DuplicatedMatchingDivisionException();
+ }
+ }
+
+ private MatchedCrews generateMatchedCrews(LinkedList crews) {
+ var matchedCrews = new MatchedCrews();
+
+ while (crews.size() > 3) {
+ matchedCrews.add(generateMatchedCrew(crews, 2));
+ }
+
+ matchedCrews.add(generateMatchedCrew(crews, crews.size()));
+
+ return matchedCrews;
+ }
+
+ private MatchedCrew generateMatchedCrew(Queue crewQueue, final int size) {
+ var matchedCrew = new MatchedCrew();
+ for (int i = 0; i < size; i++) {
+ matchedCrew.add(crewQueue.poll());
+ }
+ return matchedCrew;
+ }
+
+ public String findByMatchingDivision(MatchingDivision matchingDivision) {
+ if (!this.matchingResult.containsKey(matchingDivision)) {
+ throw new MatchingDivisionDidNotExists();
+ }
+ return this.matchingResult.get(matchingDivision).result();
+ }
+
+
+}
diff --git a/src/main/java/pairmatching/exception/BeforeMatchedCrewException.java b/src/main/java/pairmatching/exception/BeforeMatchedCrewException.java
new file mode 100644
index 0000000..c6bf274
--- /dev/null
+++ b/src/main/java/pairmatching/exception/BeforeMatchedCrewException.java
@@ -0,0 +1,11 @@
+package pairmatching.exception;
+
+public class BeforeMatchedCrewException extends RuntimeException {
+ public BeforeMatchedCrewException(final String message) {
+ super(message);
+ }
+
+ public BeforeMatchedCrewException() {
+ super("이전에 매칭된 경험이 있는 크루가 존재합니다.");
+ }
+}
diff --git a/src/main/java/pairmatching/exception/DuplicatedMatchingDivisionException.java b/src/main/java/pairmatching/exception/DuplicatedMatchingDivisionException.java
new file mode 100644
index 0000000..1b87e1f
--- /dev/null
+++ b/src/main/java/pairmatching/exception/DuplicatedMatchingDivisionException.java
@@ -0,0 +1,12 @@
+package pairmatching.exception;
+
+public class DuplicatedMatchingDivisionException extends RuntimeException {
+
+ public DuplicatedMatchingDivisionException(final String message) {
+ super(message);
+ }
+
+ public DuplicatedMatchingDivisionException() {
+ super("");
+ }
+}
diff --git a/src/main/java/pairmatching/exception/MatchingDivisionDidNotExists.java b/src/main/java/pairmatching/exception/MatchingDivisionDidNotExists.java
new file mode 100644
index 0000000..816d947
--- /dev/null
+++ b/src/main/java/pairmatching/exception/MatchingDivisionDidNotExists.java
@@ -0,0 +1,5 @@
+package pairmatching.exception;
+
+public class MatchingDivisionDidNotExists extends RuntimeException {
+
+}
diff --git a/src/main/java/pairmatching/launcher/PairmatchingLauncher.java b/src/main/java/pairmatching/launcher/PairmatchingLauncher.java
new file mode 100644
index 0000000..e5bb269
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/PairmatchingLauncher.java
@@ -0,0 +1,41 @@
+package pairmatching.launcher;
+
+import pairmatching.domain.generator.CrewGenerator;
+import pairmatching.domain.generator.ReadFileImpl;
+import pairmatching.domain.generator.ShuffleGeneratorImpl;
+import pairmatching.launcher.context.PairmatchingContext;
+import pairmatching.launcher.context.PairmatchingContextImpl;
+import pairmatching.launcher.status.InitStatus;
+import pairmatching.launcher.status.PairmatchingStatus;
+import pairmatching.launcher.status.QuitStatus;
+import pairmatching.view.InputView;
+import pairmatching.view.OutputView;
+import pairmatching.view.PairmatchingView;
+
+public class PairmatchingLauncher {
+ private final PairmatchingContext pairmatchingContext;
+ private final InputView inputView = new InputView();
+ private final OutputView outputView = new OutputView();
+
+ private PairmatchingStatus pairmatchingStatus = new InitStatus();
+
+ public PairmatchingLauncher() {
+ var pairMatchingView = new PairmatchingView(inputView, outputView);
+ var crewGenerator = new CrewGenerator(new ReadFileImpl(), new ShuffleGeneratorImpl());
+
+ this.pairmatchingContext = new PairmatchingContextImpl(pairMatchingView, crewGenerator);
+ }
+
+ public void execute() {
+ while (pairmatchingStatus.runnable()) {
+ try {
+ pairmatchingStatus = pairmatchingStatus.next(pairmatchingContext);
+ } catch (IllegalArgumentException exception) {
+ outputView.printError(exception.getMessage());
+ } catch (IllegalStateException exception) {
+ outputView.printError(exception.getMessage());
+ pairmatchingStatus = new QuitStatus();
+ }
+ }
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/code/CommandToStatusConvertor.java b/src/main/java/pairmatching/launcher/code/CommandToStatusConvertor.java
new file mode 100644
index 0000000..fdba4e3
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/code/CommandToStatusConvertor.java
@@ -0,0 +1,44 @@
+package pairmatching.launcher.code;
+
+import java.util.Arrays;
+import pairmatching.launcher.status.FindMatchingResultStatus;
+import pairmatching.launcher.status.InputStatus;
+import pairmatching.launcher.status.MatchingInitializeStatus;
+import pairmatching.launcher.status.MatchingProcessStatus;
+import pairmatching.launcher.status.PairmatchingStatus;
+import pairmatching.launcher.status.QuitStatus;
+
+public enum CommandToStatusConvertor {
+ MATCHING(FeatureCommand.MATCHING, new InputStatus(new MatchingProcessStatus())),
+ FIND(FeatureCommand.FIND, new InputStatus(new FindMatchingResultStatus())),
+ INITIALIZE(FeatureCommand.INITIALIZE, new MatchingInitializeStatus()),
+ QUIT(FeatureCommand.QUIT, new QuitStatus()),
+ UNKNOWN();
+
+
+ private final FeatureCommand command;
+ private final PairmatchingStatus status;
+
+ CommandToStatusConvertor(FeatureCommand command, PairmatchingStatus status) {
+ this.command = command;
+ this.status = status;
+ }
+
+ CommandToStatusConvertor(FeatureCommand command) {
+ this.command = command;
+ this.status = new QuitStatus();
+ }
+
+ CommandToStatusConvertor() {
+ this.command = null;
+ this.status = new QuitStatus();
+ }
+
+ public static PairmatchingStatus getNextStatus(FeatureCommand featureCommand) {
+ return Arrays.stream(values())
+ .filter(convertor -> convertor.command.equals(featureCommand))
+ .findAny()
+ .orElse(UNKNOWN)
+ .status;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/code/FeatureCommand.java b/src/main/java/pairmatching/launcher/code/FeatureCommand.java
new file mode 100644
index 0000000..b08a305
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/code/FeatureCommand.java
@@ -0,0 +1,33 @@
+package pairmatching.launcher.code;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+public enum FeatureCommand {
+ MATCHING("1", "페어 매칭"),
+ FIND("2", "페어 조회"),
+ INITIALIZE("3", "페어 초기화"),
+ QUIT("Q", "종료");
+
+
+ private static final String MESSAGE_FORMAT = "%s. %s";
+ private final String command;
+ private final String message;
+
+ FeatureCommand(final String command, final String message) {
+ this.command = command;
+ this.message = message;
+ }
+
+ public static FeatureCommand from(String input) {
+ return Arrays.stream(values())
+ .filter(featureCommand -> featureCommand.command.equals(input)).findAny()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+
+ public static String messages() {
+ return Arrays.stream(values())
+ .map(featureCommand -> String.format(MESSAGE_FORMAT, featureCommand.command, featureCommand.message))
+ .collect(Collectors.joining("\n"));
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/code/RematchingCommand.java b/src/main/java/pairmatching/launcher/code/RematchingCommand.java
new file mode 100644
index 0000000..8e76709
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/code/RematchingCommand.java
@@ -0,0 +1,30 @@
+package pairmatching.launcher.code;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+public enum RematchingCommand {
+ YES("네"), NO("아니오");
+
+ private final String command;
+
+ RematchingCommand(final String command) {
+ this.command = command;
+ }
+
+ public static RematchingCommand from(final String input) {
+ return Arrays.stream(values())
+ .filter(rematchingCommand -> rematchingCommand.command.equals(input.trim()))
+ .findAny().orElseThrow(IllegalArgumentException::new);
+ }
+
+ public static String message() {
+ return Arrays.stream(values())
+ .map(rematchingCommand -> rematchingCommand.command)
+ .collect(Collectors.joining(" | "));
+ }
+
+ public boolean isRematching() {
+ return this == YES;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pairmatching/launcher/context/PairmatchingContext.java b/src/main/java/pairmatching/launcher/context/PairmatchingContext.java
new file mode 100644
index 0000000..9f676a9
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/context/PairmatchingContext.java
@@ -0,0 +1,24 @@
+package pairmatching.launcher.context;
+
+import pairmatching.domain.matching.MatchingDivision;
+import pairmatching.view.PairmatchingView;
+
+public interface PairmatchingContext {
+
+
+ PairmatchingView getPairmatchingView();
+
+ void inputMatchingDivision(MatchingDivision matchingDivision);
+
+ String matchPair();
+
+ String rematchPair();
+
+ String findMatchedCrewsByMatchingDivision();
+
+ void plusTryCount();
+
+ void initializeRetryCount();
+
+ void initializeMatchingResult();
+}
diff --git a/src/main/java/pairmatching/launcher/context/PairmatchingContextImpl.java b/src/main/java/pairmatching/launcher/context/PairmatchingContextImpl.java
new file mode 100644
index 0000000..1dca7ae
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/context/PairmatchingContextImpl.java
@@ -0,0 +1,69 @@
+package pairmatching.launcher.context;
+
+import pairmatching.domain.count.RetryCount;
+import pairmatching.domain.generator.CrewGenerator;
+import pairmatching.domain.matching.MatchingDivision;
+import pairmatching.domain.matching.MatchingResult;
+import pairmatching.view.PairmatchingView;
+
+public class PairmatchingContextImpl implements PairmatchingContext {
+ private final PairmatchingView pairmatchingView;
+ private final CrewGenerator crewGenerator;
+ private MatchingResult matchingResult;
+ private MatchingDivision matchingDivision;
+ private RetryCount retryCount;
+
+ public PairmatchingContextImpl(PairmatchingView pairmatchingView, CrewGenerator crewGenerator) {
+ this.pairmatchingView = pairmatchingView;
+ this.crewGenerator = crewGenerator;
+ this.matchingResult = new MatchingResult();
+ }
+
+ public PairmatchingView getPairmatchingView() {
+ return this.pairmatchingView;
+ }
+
+ @Override
+ public void inputMatchingDivision(MatchingDivision matchingDivision) {
+ this.matchingDivision = matchingDivision;
+ }
+
+ @Override
+ public String matchPair() {
+ var generatedCrews = crewGenerator.generateCrews(this.matchingDivision.getCourse());
+
+ this.matchingResult.matchPair(this.matchingDivision, generatedCrews);
+
+ return this.matchingResult.findByMatchingDivision(matchingDivision);
+ }
+
+ @Override
+ public String rematchPair() {
+ var generatedCrews = crewGenerator.generateCrews(this.matchingDivision.getCourse());
+
+ this.matchingResult.rematchPair(this.matchingDivision, generatedCrews);
+
+ return this.matchingResult.findByMatchingDivision(matchingDivision);
+ }
+
+ @Override
+ public String findMatchedCrewsByMatchingDivision() {
+ return this.matchingResult.findByMatchingDivision(matchingDivision);
+ }
+
+ @Override
+ public void plusTryCount() {
+ this.retryCount = this.retryCount.plusRetry();
+
+ }
+
+ public void initializeRetryCount() {
+ this.retryCount = RetryCount.generateInitializeCount();
+ }
+
+ @Override
+ public void initializeMatchingResult() {
+ this.matchingResult = new MatchingResult();
+ }
+
+}
diff --git a/src/main/java/pairmatching/launcher/status/AskRematchingStatus.java b/src/main/java/pairmatching/launcher/status/AskRematchingStatus.java
new file mode 100644
index 0000000..52cc509
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/AskRematchingStatus.java
@@ -0,0 +1,24 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class AskRematchingStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var pairMatchingView = pairmatchingContext.getPairmatchingView();
+ var inputView = pairMatchingView.getInputView();
+
+ var rematchingCommand = inputView.readReMatchingCommand();
+ if (rematchingCommand.isRematching()) {
+ return new BeforeMatchingIgnoreProcessStatus();
+ }
+
+ pairmatchingContext.inputMatchingDivision(inputView.readPairmatchingDivision());
+ return new MatchingProcessStatus();
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/BeforeMatchingIgnoreProcessStatus.java b/src/main/java/pairmatching/launcher/status/BeforeMatchingIgnoreProcessStatus.java
new file mode 100644
index 0000000..1c2e79e
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/BeforeMatchingIgnoreProcessStatus.java
@@ -0,0 +1,23 @@
+package pairmatching.launcher.status;
+
+import pairmatching.exception.BeforeMatchedCrewException;
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class BeforeMatchingIgnoreProcessStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var outputView = pairmatchingContext.getPairmatchingView().getOutputView();
+ pairmatchingContext.plusTryCount();
+ try {
+ outputView.printMatchingResultCrews(pairmatchingContext.rematchPair());
+ return new SelectFeatureStatus();
+ } catch (BeforeMatchedCrewException | IllegalStateException exception) {
+ return this;
+ }
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/FindMatchingResultStatus.java b/src/main/java/pairmatching/launcher/status/FindMatchingResultStatus.java
new file mode 100644
index 0000000..bcb7002
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/FindMatchingResultStatus.java
@@ -0,0 +1,26 @@
+package pairmatching.launcher.status;
+
+import pairmatching.exception.MatchingDivisionDidNotExists;
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class FindMatchingResultStatus implements PairmatchingStatus {
+
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var pairmatchingView = pairmatchingContext.getPairmatchingView();
+ var outputView = pairmatchingView.getOutputView();
+
+ try {
+ outputView.printMatchingResultCrews(pairmatchingContext.findMatchedCrewsByMatchingDivision());
+ } catch (MatchingDivisionDidNotExists matchingDivisionDidNotExists) {
+ throw new IllegalArgumentException("매칭 이력이 없습니다.");
+ }
+
+ return new SelectFeatureStatus();
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/InitStatus.java b/src/main/java/pairmatching/launcher/status/InitStatus.java
new file mode 100644
index 0000000..c1bd3d1
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/InitStatus.java
@@ -0,0 +1,15 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class InitStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ return new SelectFeatureStatus();
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/InputStatus.java b/src/main/java/pairmatching/launcher/status/InputStatus.java
new file mode 100644
index 0000000..ea7178d
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/InputStatus.java
@@ -0,0 +1,31 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class InputStatus implements PairmatchingStatus {
+
+ private final PairmatchingStatus nextStatus;
+
+ public InputStatus(PairmatchingStatus nextStatus) {
+ this.nextStatus = nextStatus;
+ }
+
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var pairmatchingView = pairmatchingContext.getPairmatchingView();
+ var inputView = pairmatchingView.getInputView();
+ var outputView = pairmatchingView.getOutputView();
+
+ pairmatchingContext.initializeRetryCount();
+
+ outputView.printMatchingMenu();
+ pairmatchingContext.inputMatchingDivision(inputView.readPairmatchingDivision());
+
+ return nextStatus;
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/MatchingInitializeStatus.java b/src/main/java/pairmatching/launcher/status/MatchingInitializeStatus.java
new file mode 100644
index 0000000..372f7b4
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/MatchingInitializeStatus.java
@@ -0,0 +1,20 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class MatchingInitializeStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var pairmatchingView = pairmatchingContext.getPairmatchingView();
+
+ pairmatchingContext.initializeMatchingResult();
+ pairmatchingView.getOutputView().printInitializeMessage();
+
+ return new SelectFeatureStatus();
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/MatchingProcessStatus.java b/src/main/java/pairmatching/launcher/status/MatchingProcessStatus.java
new file mode 100644
index 0000000..06c5d0a
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/MatchingProcessStatus.java
@@ -0,0 +1,33 @@
+package pairmatching.launcher.status;
+
+import pairmatching.exception.BeforeMatchedCrewException;
+import pairmatching.exception.DuplicatedMatchingDivisionException;
+import pairmatching.launcher.context.PairmatchingContext;
+import pairmatching.view.OutputView;
+
+public class MatchingProcessStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var pairmatchingView = pairmatchingContext.getPairmatchingView();
+ var outputView = pairmatchingView.getOutputView();
+
+ pairmatchingContext.plusTryCount();
+ return this.getNextStatus(pairmatchingContext, outputView);
+ }
+
+ private PairmatchingStatus getNextStatus(PairmatchingContext pairmatchingContext, OutputView outputView) {
+ try {
+ outputView.printMatchingResultCrews(pairmatchingContext.matchPair());
+ return new SelectFeatureStatus();
+ } catch (DuplicatedMatchingDivisionException duplicatedMatchingDivisionException) {
+ return new AskRematchingStatus();
+ } catch (BeforeMatchedCrewException | IllegalStateException exception) {
+ return this;
+ }
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/PairmatchingStatus.java b/src/main/java/pairmatching/launcher/status/PairmatchingStatus.java
new file mode 100644
index 0000000..6d9a066
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/PairmatchingStatus.java
@@ -0,0 +1,9 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.context.PairmatchingContext;
+
+public interface PairmatchingStatus {
+ PairmatchingStatus next(PairmatchingContext pairmatchingContext);
+
+ boolean runnable();
+}
diff --git a/src/main/java/pairmatching/launcher/status/QuitStatus.java b/src/main/java/pairmatching/launcher/status/QuitStatus.java
new file mode 100644
index 0000000..8ea6d5d
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/QuitStatus.java
@@ -0,0 +1,15 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class QuitStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ return null;
+ }
+
+ @Override
+ public boolean runnable() {
+ return false;
+ }
+}
diff --git a/src/main/java/pairmatching/launcher/status/SelectFeatureStatus.java b/src/main/java/pairmatching/launcher/status/SelectFeatureStatus.java
new file mode 100644
index 0000000..cbcd80b
--- /dev/null
+++ b/src/main/java/pairmatching/launcher/status/SelectFeatureStatus.java
@@ -0,0 +1,20 @@
+package pairmatching.launcher.status;
+
+import pairmatching.launcher.code.CommandToStatusConvertor;
+import pairmatching.launcher.context.PairmatchingContext;
+
+public class SelectFeatureStatus implements PairmatchingStatus {
+ @Override
+ public PairmatchingStatus next(PairmatchingContext pairmatchingContext) {
+ var pairmatchingView = pairmatchingContext.getPairmatchingView();
+ var featureCommand = pairmatchingView.getInputView().readFeatureCommand();
+
+ pairmatchingContext.initializeRetryCount();
+ return CommandToStatusConvertor.getNextStatus(featureCommand);
+ }
+
+ @Override
+ public boolean runnable() {
+ return true;
+ }
+}
diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java
new file mode 100644
index 0000000..b243e72
--- /dev/null
+++ b/src/main/java/pairmatching/view/InputView.java
@@ -0,0 +1,43 @@
+package pairmatching.view;
+
+import camp.nextstep.edu.missionutils.Console;
+import java.util.NoSuchElementException;
+import pairmatching.domain.matching.MatchingDivision;
+import pairmatching.launcher.code.FeatureCommand;
+import pairmatching.launcher.code.RematchingCommand;
+
+public class InputView extends IoPrinter {
+
+ public FeatureCommand readFeatureCommand() {
+ this.println("기능을 선택하세요.");
+ this.println(FeatureCommand.messages());
+ return FeatureCommand.from(this.readLineAfterPrintNewLine());
+ }
+
+ public MatchingDivision readPairmatchingDivision() {
+
+ this.println("과정, 레벨, 미션을 선택하세요.");
+ this.println("ex) 백엔드, 레벨1, 자동차경주");
+ return new MatchingDivision(this.readLineAfterPrintNewLine());
+ }
+
+ private String readLine() {
+ try {
+ return Console.readLine();
+ } catch (NoSuchElementException exception) {
+ throw new IllegalStateException("더 이상 입력할 수 없습니다.");
+ }
+ }
+
+ public RematchingCommand readReMatchingCommand() {
+ this.println("매칭 정보가 있습니다. 다시 매칭하시겠습니까?");
+ this.println(RematchingCommand.message());
+ return RematchingCommand.from(this.readLineAfterPrintNewLine());
+ }
+
+ private String readLineAfterPrintNewLine() {
+ var input = this.readLine();
+ this.println();
+ return input;
+ }
+}
diff --git a/src/main/java/pairmatching/view/IoPrinter.java b/src/main/java/pairmatching/view/IoPrinter.java
new file mode 100644
index 0000000..4905b42
--- /dev/null
+++ b/src/main/java/pairmatching/view/IoPrinter.java
@@ -0,0 +1,19 @@
+package pairmatching.view;
+
+public class IoPrinter {
+ protected static final String DIVISION_HASH = "#############################################";
+
+ protected void print(final Object message) {
+ System.out.print(message);
+ }
+
+ protected void println(final Object message) {
+ System.out.println(message);
+ }
+
+ protected void println() {
+ this.println("");
+ }
+
+
+}
diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java
new file mode 100644
index 0000000..4537ce9
--- /dev/null
+++ b/src/main/java/pairmatching/view/OutputView.java
@@ -0,0 +1,27 @@
+package pairmatching.view;
+
+import pairmatching.domain.code.Course;
+import pairmatching.domain.code.Level;
+
+public class OutputView extends IoPrinter {
+
+ public void printMatchingResultCrews(String matchedCrewNames) {
+ this.println("페어 매칭 결과입니다.");
+ this.println(matchedCrewNames);
+ }
+
+ public void printInitializeMessage() {
+ this.println("초기화 되었습니다. ");
+ }
+
+ public void printError(final Object message) {
+ this.println("[ERROR]" + message);
+ }
+
+ public void printMatchingMenu() {
+ this.println(DIVISION_HASH);
+ this.println(String.format("과정 : %s", Course.messages()));
+ this.println(Level.messages());
+ this.println(DIVISION_HASH);
+ }
+}
diff --git a/src/main/java/pairmatching/view/PairmatchingView.java b/src/main/java/pairmatching/view/PairmatchingView.java
new file mode 100644
index 0000000..3d681c3
--- /dev/null
+++ b/src/main/java/pairmatching/view/PairmatchingView.java
@@ -0,0 +1,19 @@
+package pairmatching.view;
+
+public class PairmatchingView {
+ private final InputView inputView;
+ private final OutputView outputView;
+
+ public PairmatchingView(InputView inputView, OutputView outputView) {
+ this.inputView = inputView;
+ this.outputView = outputView;
+ }
+
+ public InputView getInputView() {
+ return inputView;
+ }
+
+ public OutputView getOutputView() {
+ return outputView;
+ }
+}
diff --git a/src/test/java/pairmatching/ApplicationTest.java b/src/test/java/pairmatching/ApplicationTest.java
index 289af26..d2d7701 100644
--- a/src/test/java/pairmatching/ApplicationTest.java
+++ b/src/test/java/pairmatching/ApplicationTest.java
@@ -11,6 +11,7 @@
class ApplicationTest extends NsTest {
private static final String ERROR_MESSAGE = "[ERROR]";
+
@Test
void 짝수_인원_페어_매칭() {
assertShuffleTest(
@@ -31,6 +32,22 @@ class ApplicationTest extends NsTest {
}
);
}
+
+ @Test
+ void _3회_실패에_대한_에러처리() {
+ assertShuffleTest(
+ () -> {
+ var command = new String[]{"1", "백엔드,레벨1,자동차경주", "1", "백엔드,레벨1,로또"};
+ runException(command);
+ assertThat(output()).contains(ERROR_MESSAGE);
+ },
+ Arrays.asList("태웅", "백호", "치수", "태섭"),
+ Arrays.asList("태웅", "백호", "치수", "태섭"),
+ Arrays.asList("태웅", "백호", "치수", "태섭"),
+ Arrays.asList("태웅", "백호", "치수", "태섭")
+ );
+ }
+
@Override
public void runMain() {
Application.main(new String[]{});
diff --git a/src/test/java/pairmatching/domain/code/CourseTest.java b/src/test/java/pairmatching/domain/code/CourseTest.java
new file mode 100644
index 0000000..7df577e
--- /dev/null
+++ b/src/test/java/pairmatching/domain/code/CourseTest.java
@@ -0,0 +1,28 @@
+package pairmatching.domain.code;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class CourseTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {"풀스택", " ", ""})
+ void 잘못된_입력을_할_경우_오류를_반환합니다(final String input) {
+ Assertions.assertThatIllegalArgumentException().isThrownBy(() -> Course.from(input));
+ }
+
+ @Test
+ void 과정에_대한_메시지를_반환할_수_있습니다() {
+ var actual = Course.messages();
+ var expected = "백엔드 | 프론트엔드";
+
+ Assertions.assertThat(actual).isEqualTo(expected);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/code/LevelTest.java b/src/test/java/pairmatching/domain/code/LevelTest.java
new file mode 100644
index 0000000..1a0536f
--- /dev/null
+++ b/src/test/java/pairmatching/domain/code/LevelTest.java
@@ -0,0 +1,39 @@
+package pairmatching.domain.code;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class LevelTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {"레벨0", "레벨", "레벨6"})
+ void 등록되지_않는_레벨에_대한_입력은_오류를_발생시킵니다(final String input) {
+ Assertions.assertThatIllegalArgumentException()
+ .isThrownBy(() -> Level.from(input, Subject.RACING));
+ }
+
+ @ParameterizedTest
+ @CsvSource(value = {"레벨1:장바구니", "레벨2:배포"}, delimiterString = ":")
+ void 레벨에_맞지_않는_과목이면_오류를_발생시킵니다(final String levelName, final String subjectName) {
+ Assertions.assertThatIllegalArgumentException()
+ .isThrownBy(() -> Level.from(levelName, Subject.from(subjectName)));
+ }
+
+ @Test
+ void 레벨에_대한_메시지를_확인할_수_있습니다() {
+ var actual = Level.messages();
+ var expected = " - 레벨1: 자동차경주 | 로또 | 숫자야구게임\n" +
+ " - 레벨2: 장바구니 | 결제 | 지하철노선도\n" +
+ " - 레벨3: \n" +
+ " - 레벨4: 성능개선 | 배포\n" +
+ " - 레벨5: ";
+
+ Assertions.assertThat(actual).isEqualTo(expected);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/code/SubjectTest.java b/src/test/java/pairmatching/domain/code/SubjectTest.java
new file mode 100644
index 0000000..45fda0d
--- /dev/null
+++ b/src/test/java/pairmatching/domain/code/SubjectTest.java
@@ -0,0 +1,19 @@
+package pairmatching.domain.code;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class SubjectTest {
+
+
+ @ParameterizedTest
+ @ValueSource(strings = {"자동차", "로", " ", ""})
+ void 잘못된_과제를_입력하면_오류를_발생시킵니다(final String input) {
+ Assertions.assertThatIllegalArgumentException()
+ .isThrownBy(() -> Subject.from(input));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/generator/CrewGeneratorTest.java b/src/test/java/pairmatching/domain/generator/CrewGeneratorTest.java
new file mode 100644
index 0000000..6e12fd0
--- /dev/null
+++ b/src/test/java/pairmatching/domain/generator/CrewGeneratorTest.java
@@ -0,0 +1,44 @@
+package pairmatching.domain.generator;
+
+import java.util.List;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import pairmatching.domain.code.Course;
+import pairmatching.domain.matching.Crew;
+
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class CrewGeneratorTest {
+
+ @Test
+ void 임의의_값을_넣을때_크루가_만들어지는가() {
+ var crewGenerator = new CrewGenerator(new FakeFileReader(List.of("가나", "가나다")), new FakeShuffle());
+
+ var crews = crewGenerator.generateCrews(Course.BACKEND);
+
+ Assertions.assertThat(crews)
+ .isEqualTo(List.of(new Crew(Course.BACKEND, "가나"), new Crew(Course.BACKEND, "가나다")));
+ }
+
+
+ @Test
+ void 입력받은_파일에서_크기가_2보다_작은경우_에러를_발생시킵니다() {
+ Assertions.assertThatIllegalStateException()
+ .isThrownBy(() -> {
+ var generator = new CrewGenerator(new FakeFileReader(List.of("가나")), new FakeShuffle());
+ generator.generateCrews(Course.BACKEND);
+ });
+ }
+
+ @Test
+ void 입력받은_파일에서_중복되는_이름을_가지는_크루가_있으면_에러를_발생시킵니다() {
+ Assertions.assertThatIllegalStateException()
+ .isThrownBy(() -> {
+ var generator = new CrewGenerator(new FakeFileReader(List.of("가나", "가나")), new FakeShuffle());
+ generator.generateCrews(Course.BACKEND);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/generator/FakeFileReader.java b/src/test/java/pairmatching/domain/generator/FakeFileReader.java
new file mode 100644
index 0000000..9f7dda8
--- /dev/null
+++ b/src/test/java/pairmatching/domain/generator/FakeFileReader.java
@@ -0,0 +1,17 @@
+package pairmatching.domain.generator;
+
+import java.util.List;
+
+public class FakeFileReader implements ReadFile {
+
+ private final List data;
+
+ public FakeFileReader(List data) {
+ this.data = data;
+ }
+
+ @Override
+ public List readFile(String fileName) {
+ return data;
+ }
+}
diff --git a/src/test/java/pairmatching/domain/generator/FakeShuffle.java b/src/test/java/pairmatching/domain/generator/FakeShuffle.java
new file mode 100644
index 0000000..8e95ad8
--- /dev/null
+++ b/src/test/java/pairmatching/domain/generator/FakeShuffle.java
@@ -0,0 +1,10 @@
+package pairmatching.domain.generator;
+
+import java.util.List;
+
+public class FakeShuffle implements ShuffleGenerator {
+ @Override
+ public List shuffle(List input) {
+ return input;
+ }
+}
diff --git a/src/test/java/pairmatching/domain/matching/CrewTest.java b/src/test/java/pairmatching/domain/matching/CrewTest.java
new file mode 100644
index 0000000..9743832
--- /dev/null
+++ b/src/test/java/pairmatching/domain/matching/CrewTest.java
@@ -0,0 +1,39 @@
+package pairmatching.domain.matching;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import pairmatching.domain.code.Course;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class CrewTest {
+
+
+ @ParameterizedTest
+ @ValueSource(strings = {" ", " ", " "})
+ void 크루의_이름은_비어있을_수_없습니다(final String input) {
+ Assertions.assertThatIllegalArgumentException()
+ .isThrownBy(() -> new Crew(Course.BACKEND, input));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"가", "나다"})
+ void 크루의_이름과_강좌가_같으면_같은_크루입니다(final String input) {
+ var actual = new Crew(Course.BACKEND, input);
+ var expected = new Crew(Course.BACKEND, input);
+
+ Assertions.assertThat(actual).isEqualTo(expected);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"가", "나"})
+ void 크루의_이름이_같고_강좌가_다르면_다른_크루입니다(final String input) {
+ var target = new Crew(Course.BACKEND, input);
+ var original = new Crew(Course.FRONTEND, input);
+
+ Assertions.assertThat(original).isNotEqualTo(target);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/matching/MatchedCrewsTest.java b/src/test/java/pairmatching/domain/matching/MatchedCrewsTest.java
new file mode 100644
index 0000000..6c1e0f3
--- /dev/null
+++ b/src/test/java/pairmatching/domain/matching/MatchedCrewsTest.java
@@ -0,0 +1,33 @@
+package pairmatching.domain.matching;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import pairmatching.domain.code.Course;
+import pairmatching.utils.TestUtils;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class MatchedCrewsTest {
+
+ @Test
+ void 매칭된_크루_목록의_결과를_반환할_수_있습니다() {
+ var matchedCrews = new MatchedCrews();
+
+ matchedCrews.add(generateMatchedCrew(Course.BACKEND, "마,바"));
+ matchedCrews.add(generateMatchedCrew(Course.BACKEND, "가,나,다"));
+
+ var actual = matchedCrews.result().split("\n");
+
+ Assertions.assertThat(actual[0]).isEqualTo("마 : 바");
+ Assertions.assertThat(actual[1]).isEqualTo("가 : 나 : 다");
+ }
+
+
+ private MatchedCrew generateMatchedCrew(Course course, final String name) {
+ var matchedCrew = new MatchedCrew();
+ matchedCrew.addAll(TestUtils.convertStringToCrews(course, name));
+ return matchedCrew;
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/matching/MatchingDivisionTest.java b/src/test/java/pairmatching/domain/matching/MatchingDivisionTest.java
new file mode 100644
index 0000000..766df94
--- /dev/null
+++ b/src/test/java/pairmatching/domain/matching/MatchingDivisionTest.java
@@ -0,0 +1,37 @@
+package pairmatching.domain.matching;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class MatchingDivisionTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {"가,", "", " ", " , , , "})
+ void 구분자를_통해서_매칭_구분값을_입력받을_수_있습니다(final String input) {
+ Assertions.assertThatIllegalArgumentException()
+ .isThrownBy(() -> new MatchingDivision(input));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"백엔드,레벨1,자동차경주"})
+ void 구분과정이_같으면_같은_과정입니다(final String input) {
+ var expected = new MatchingDivision(input);
+ var actual = new MatchingDivision(input);
+ Assertions.assertThat(actual).isEqualTo(expected);
+ }
+
+ @ParameterizedTest
+ @CsvSource(value = {"백엔드,레벨1,자동차경주:백엔드,레벨1,로또"}, delimiterString = ":")
+ void 구분값이_다르면_다른_과정입니다(final String originalInput, final String targetInput) {
+ var original = new MatchingDivision(originalInput);
+ var target = new MatchingDivision(targetInput);
+
+ Assertions.assertThat(original).isNotEqualTo(target);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/domain/matching/MatchingResultTest.java b/src/test/java/pairmatching/domain/matching/MatchingResultTest.java
new file mode 100644
index 0000000..02aee88
--- /dev/null
+++ b/src/test/java/pairmatching/domain/matching/MatchingResultTest.java
@@ -0,0 +1,53 @@
+package pairmatching.domain.matching;
+
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static pairmatching.utils.TestUtils.convertStringToCrews;
+
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import pairmatching.domain.code.Course;
+import pairmatching.exception.BeforeMatchedCrewException;
+import pairmatching.exception.DuplicatedMatchingDivisionException;
+import pairmatching.exception.MatchingDivisionDidNotExists;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class MatchingResultTest {
+
+ @Test
+ void 중복되는_키가_있는_경우_에러를_발생시킵니다() {
+ var matchingResult = new MatchingResult();
+ var matchingDivision = new MatchingDivision("백엔드,레벨1,자동차경주");
+
+ assertThatThrownBy(() -> {
+ matchingResult.matchPair(matchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다"));
+ matchingResult.matchPair(matchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다"));
+ }).isInstanceOf(DuplicatedMatchingDivisionException.class);
+ }
+
+ @Test
+ void 키를_통해서_매칭_결과를_조회할때_데이터가_없는_경우_에러를_발생시킵니다() {
+ var matchingResult = new MatchingResult();
+ var matchingDivision = new MatchingDivision("백엔드,레벨1,자동차경주");
+
+ assertThatThrownBy(() -> {
+ matchingResult.findByMatchingDivision(matchingDivision);
+ }).isInstanceOf(MatchingDivisionDidNotExists.class);
+ }
+
+ @Test
+ void 같은_레벨에서_페어를_맺은적이_있는_크루가_있는_경우_오류를_발생시킵니다() {
+ var matchingResult = new MatchingResult();
+ var matchingDivision = new MatchingDivision("백엔드,레벨1,자동차경주");
+
+ var newMatchingDivision = new MatchingDivision("백엔드,레벨1,로또");
+ assertThatThrownBy(() -> {
+ matchingResult.matchPair(matchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다"));
+
+ matchingResult.matchPair(newMatchingDivision, convertStringToCrews(Course.BACKEND, "가,나,다"));
+ }).isInstanceOf(BeforeMatchedCrewException.class);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/launcher/code/FeatureCommandTest.java b/src/test/java/pairmatching/launcher/code/FeatureCommandTest.java
new file mode 100644
index 0000000..90eff96
--- /dev/null
+++ b/src/test/java/pairmatching/launcher/code/FeatureCommandTest.java
@@ -0,0 +1,28 @@
+package pairmatching.launcher.code;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class FeatureCommandTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {" ", "4", "q"})
+ void 잘못된_입력어를_입력하면_오류를_반환합니다(final String input) {
+ Assertions.assertThatIllegalArgumentException().isThrownBy(() -> FeatureCommand.from(input));
+ }
+
+ @Test
+ void 기능명령어는_메시지를_가집니다() {
+ var actual = FeatureCommand.messages();
+ var expected = "1. 페어 매칭\n" +
+ "2. 페어 조회\n" +
+ "3. 페어 초기화\n" +
+ "Q. 종료";
+ Assertions.assertThat(actual).isEqualTo(expected);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/launcher/context/FakePairmatchingContext.java b/src/test/java/pairmatching/launcher/context/FakePairmatchingContext.java
new file mode 100644
index 0000000..f1d4e80
--- /dev/null
+++ b/src/test/java/pairmatching/launcher/context/FakePairmatchingContext.java
@@ -0,0 +1,49 @@
+package pairmatching.launcher.context;
+
+import pairmatching.domain.matching.MatchingDivision;
+import pairmatching.view.InputView;
+import pairmatching.view.OutputView;
+import pairmatching.view.PairmatchingView;
+
+public class FakePairmatchingContext implements PairmatchingContext {
+
+ @Override
+ public PairmatchingView getPairmatchingView() {
+ return new PairmatchingView(new InputView(), new OutputView());
+ }
+
+ @Override
+ public void inputMatchingDivision(MatchingDivision matchingDivision) {
+
+ }
+
+ @Override
+ public String matchPair() {
+ return null;
+ }
+
+ @Override
+ public String rematchPair() {
+ return null;
+ }
+
+ @Override
+ public String findMatchedCrewsByMatchingDivision() {
+ return null;
+ }
+
+ @Override
+ public void plusTryCount() {
+
+ }
+
+ @Override
+ public void initializeRetryCount() {
+
+ }
+
+ @Override
+ public void initializeMatchingResult() {
+
+ }
+}
diff --git a/src/test/java/pairmatching/launcher/status/InitStatusTest.java b/src/test/java/pairmatching/launcher/status/InitStatusTest.java
new file mode 100644
index 0000000..4a14326
--- /dev/null
+++ b/src/test/java/pairmatching/launcher/status/InitStatusTest.java
@@ -0,0 +1,21 @@
+package pairmatching.launcher.status;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+
+import pairmatching.launcher.context.FakePairmatchingContext;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class InitStatusTest {
+
+ @Test
+ void 초기화_상태_다음은_사용자_기능선택_상태입니다() {
+ var actualStatus = new InitStatus().next(new FakePairmatchingContext());
+ var expectedStatus = new SelectFeatureStatus();
+ Assertions.assertThat(actualStatus).isInstanceOf(expectedStatus.getClass());
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/launcher/status/MatchingProcessStatusTest.java b/src/test/java/pairmatching/launcher/status/MatchingProcessStatusTest.java
new file mode 100644
index 0000000..776921e
--- /dev/null
+++ b/src/test/java/pairmatching/launcher/status/MatchingProcessStatusTest.java
@@ -0,0 +1,18 @@
+package pairmatching.launcher.status;
+
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import pairmatching.launcher.context.FakePairmatchingContext;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class MatchingProcessStatusTest {
+
+
+ @Test
+ void 매칭_처리상태에서_중복된_매칭이_일어난_경우_다시매칭한다() {
+
+ new MatchingProcessStatus().next(new FakePairmatchingContext());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/launcher/status/SelectFeatureStatusTest.java b/src/test/java/pairmatching/launcher/status/SelectFeatureStatusTest.java
new file mode 100644
index 0000000..d76a6f6
--- /dev/null
+++ b/src/test/java/pairmatching/launcher/status/SelectFeatureStatusTest.java
@@ -0,0 +1,45 @@
+package pairmatching.launcher.status;
+
+import camp.nextstep.edu.missionutils.test.NsTest;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import pairmatching.launcher.context.FakePairmatchingContext;
+
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class SelectFeatureStatusTest extends NsTest {
+
+ PairmatchingStatus getNextStatus() {
+ return new SelectFeatureStatus().next(new FakePairmatchingContext());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"1", "2"})
+ void 기능선택하는상태에서_1_2를_입력하면_다음은_입력상태로_넘어간다(final String input) {
+ run(input);
+ Assertions.assertThat(this.getNextStatus())
+ .isInstanceOf(InputStatus.class);
+ }
+
+ @Test
+ void 기능선택상태에서_3을_입력하면_매칭_초기화_상태가_된다() {
+ run("3");
+ Assertions.assertThat(this.getNextStatus())
+ .isInstanceOf(MatchingInitializeStatus.class);
+ }
+
+ @Test
+ void 기능선택상태에서_Q_를_입력하면_종료상태가_된다() {
+ run("Q");
+ Assertions.assertThat(this.getNextStatus())
+ .isInstanceOf(QuitStatus.class);
+ }
+
+ @Override
+ protected void runMain() {
+
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/pairmatching/utils/TestUtils.java b/src/test/java/pairmatching/utils/TestUtils.java
new file mode 100644
index 0000000..f8de626
--- /dev/null
+++ b/src/test/java/pairmatching/utils/TestUtils.java
@@ -0,0 +1,16 @@
+package pairmatching.utils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import pairmatching.domain.code.Course;
+import pairmatching.domain.matching.Crew;
+
+public class TestUtils {
+
+ public static List convertStringToCrews(Course course, final String input) {
+ return Arrays.stream(input.split(","))
+ .map(name -> new Crew(course, name))
+ .collect(Collectors.toList());
+ }
+}