diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..70fe9960b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,121 @@ +[작성자]  +이름: 양홍주 +메일: willyouspeedup@naver.com  +  + +## 구현할 "기능 목록"을 정리 + +[페어 매칭] +- 페어 매칭에 필요한 크루들의 이름을 파일 입출력을 통해 불러온다. O + - src/main/resources/backend-crew.md과 src/main/resources/frontend-crew.md 파일을 이용한다. O +- 크루들의 이름 목록을 List 형태로 준비한다. O +- 크루 목록의 순서를 랜덤으로 섞는다. 이 때 `camp.nextstep.edu.missionutils.Randoms`의 shuffle 메서드를 활용한다. O +- 랜덤으로 섞인 페어 목록에서 페어 매칭을 할 때 앞에서부터 순서대로 두명씩 페어를 맺는다. O +- 홀수인 경우 마지막 남은 크루는 마지막 페어에 포함시킨다. O + - 이 경우 한 페어는 3인으로 구성한다. O +- 같은 레벨에서 이미 페어로 만난적이 있는 크루끼리 다시 페어로 매칭 된다면 크루 목록의 순서를 다시 랜덤으로 섞어서 매칭을 시도한다. O + - 3회 시도까지 매칭이 되지 않거나 매칭을 할 수 있는 경우의 수가 없으면 에러 메시지를 출력한다. O + + +[페어 조회 기능] +- 과정, 레벨, 미션을 선택하면 해당 미션의 페어 정보를 출력한다. O +- 매칭 이력이 없으면 매칭 이력이 없다고 안내한다. O + - 이 때, 에러 메세지는 다음과 같다. "[ERROR] 매칭 이력이 없습니다." O + + +[페어 초기화 기능] +- 페어 매칭 정보를 모두 초기화시킨다. O +   +[사전 제공 정보] +- 과정 O + - 백엔드 O + - 프론트엔드 O +- 레벨 O + - 레벨1 O + - 레벨2 O + - 레벨3 O + - 레벨4 O + - 레벨5 O +- 미션 O + - 레벨1 O + - 자동차경주 O + - 로또 O + - 숫자야구게임 O + - 레벨2 O + - 장바구니 O + - 결제 O + - 지하철노선도 O + - 레벨3 (페어메칭 없음) O + - 레벨4 O + - 성능개선 O + - 배포 O + - 레벨5 (페어메칭 없음) O + +- 크루 정보는 src/resources 하위에 md 파일로 제공된다. O + - 백엔드, 프론트엔드 각각 파일이 존재한다. O + +  +[입력 화면] +- 사용자가 잘못된 값을 입력하면 IllegalArgumentException와 [ERROR]로 시작하는 에러 메시지를 출력 후 해당 부분부터 다시 입력을 받는다. O +- 사용자는 "기능 메뉴 선택" 정수 1개를 입력 O + - 1번은 페어 매칭, 2번은 페어 조회, 3번은 페어 초기화, Q는 종료 O + - 검증: 만약 없는 메뉴를 입력하면 잘못 입력한 것. O + - 오타 교정: 앞뒤로 공백을 제거해줌 O +- 사용자는 "과정, 레벨, 미션"을 ,로 구분하여 한 줄에 입력 O + - 검증: 없는 과정을 입력하면 잘못된 입력 O + - 검증: 없는 레벨을 입력하면 잘못된 입력 O + - 검증: 페어매칭이 없는 과정을 입력하면 잘못된 입력 O + - 검증: 선택한 과정에 입력한 미션이 없으면 잘못된 입력 O + - 오타 교정: 입력 항목 사이에 불필요한 ,는 자동으로 제거해줌 O + - 오타 교정: 전체 입력 문장에서 모든 공백을 제거해줌. O +- 이미 매칭 정보가 있는 경우 재매칭할지에 대한 정보를 입력받는다. O + - 사용자는 문자 하나를 입력한다. O + - 예를 선택할 경우 페어를 재매칭한다. O + - 아니오를 선택할 경우 코스, 레벨, 미션을 다시 선택한다. O + - 검증: 예, 아니오가 아닌 값을 입력하면 잘못입력한 것. O + +[출력 화면] +- 과정와 미션을 출력 O +``` +############################################# +과정: 백엔드 | 프론트엔드 +미션: + - 레벨1: 자동차경주 | 로또 | 숫자야구게임 + - 레벨2: 장바구니 | 결제 | 지하철노선도 + - 레벨3: + - 레벨4: 성능개선 | 배포 + - 레벨5: +############################################ +``` +- 페어 매칭 결과 출력 O +``` +페어 매칭 결과입니다. +이브 : 윌터 +보노 : 제키 +신디 : 로드 +제시 : 린다 +시저 : 라라 +니콜 : 다비 +리사 : 덴버 : 제키 +``` + + +## 커밋 컨벤션 + +> 기본 형태 +~~~ +type: subject + +body +~~~ +  +> type + +feat: 새로운 기능  +fix: 버그 수정  +docs: 문서 수정  +style: 서식 지정, 세미콜론 누락 등 (코드 변경 없음)  +refactor: 코드 리팩토링  +test: 테스트 코드 추가, 테스트 코드 리팩토링 (생산 코드 변경 없음)  +chore: 빌드, 패키지 매니저 등 업데이트 (생산 코드 변경 없음)  +  diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index 6f56e741c..b71275d50 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,7 +1,19 @@ package pairmatching; +import pairmatching.controller.MatchingController; +import pairmatching.repository.MatchingResultRepository; +import pairmatching.service.MatchingService; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO 구현 진행 + MatchingController matchingController = new MatchingController( + new InputView(), + new OutputView(), + new MatchingService(new MatchingResultRepository()) + ); + + matchingController.play(); } } diff --git a/src/main/java/pairmatching/controller/MatchingController.java b/src/main/java/pairmatching/controller/MatchingController.java new file mode 100644 index 000000000..cf6fc4ed8 --- /dev/null +++ b/src/main/java/pairmatching/controller/MatchingController.java @@ -0,0 +1,166 @@ +package pairmatching.controller; + +import static pairmatching.domain.entity.Function.PAIR_CHECK; +import static pairmatching.domain.entity.Function.PAIR_MATCHING; +import static pairmatching.domain.entity.Function.PAIR_RESET; +import static pairmatching.domain.entity.Function.QUIT; +import static pairmatching.domain.entity.RematchingOption.YES; +import static pairmatching.messages.ErrorMessages.FAIL_TO_MATCH; +import static pairmatching.messages.ErrorMessages.INVALID_COURSE_MISSION; +import static pairmatching.messages.ErrorMessages.INVALID_FUNCTION; +import static pairmatching.messages.ErrorMessages.INVALID_REMATCHING_SELECT; +import static pairmatching.messages.ErrorMessages.NOT_EXIST_MATCHING_RESULT; + +import java.util.Optional; +import pairmatching.domain.dto.MatchingResultMapper; +import pairmatching.domain.entity.Course; +import pairmatching.domain.entity.CourseMission; +import pairmatching.domain.entity.Function; +import pairmatching.domain.entity.MatchingResult; +import pairmatching.domain.entity.RematchingOption; +import pairmatching.service.MatchingService; +import pairmatching.util.InputUtil; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +public class MatchingController { + + private final int REMATCHING_COUNT = 3; + + private final InputView inputView; + private final OutputView outputView; + private final MatchingService matchingService; + + public MatchingController(InputView inputView, OutputView outputView, MatchingService matchingService) { + this.inputView = inputView; + this.outputView = outputView; + this.matchingService = matchingService; + } + + public void play() { + + while (true) { + final Function function = inputValidFunction(); + + if (function.equals(QUIT)) { + return; + } + processFunctionInput(function); + } + } + + private void processFunctionInput(Function function) { + processPairMatching(function); + processPairCheck(function); + processPairReset(function); + } + + private void processPairReset(Function function) { + if (function.equals(PAIR_RESET)) { + matchingService.resetMatching(); + outputView.ouputReset(); + } + } + + private void processPairCheck(Function function) { + if (function.equals(PAIR_CHECK)) { + outputView.outputCourseMission(); + final CourseMission courseMission = inputValidCourseMission(); + + checkMatchingResult(courseMission); + } + } + + private void checkMatchingResult(CourseMission courseMission) { + Optional matchingResult = matchingService.findMatchingResult(courseMission); + + if (!matchingResult.isPresent()) { + outputView.outputErrorMessage(NOT_EXIST_MATCHING_RESULT.getMessage()); + return; + } + outputView.outputPairMatchingResult(MatchingResultMapper.from(matchingResult.get())); + } + + private void processPairMatching(Function function) { + if (function.equals(PAIR_MATCHING)) { + outputView.outputCourseMission(); + final CourseMission courseMission = inputValidCourseMission(); + Course course = courseMission.getCourse(); + + processExistResult(courseMission, course); + } + } + + private void processExistResult(CourseMission courseMission, Course course) { + Optional foundMatchingResult = matchingService.findMatchingResult(courseMission); + + if (foundMatchingResult.isPresent()) { + RematchingOption rematchingOption = inputValidRematchingOption(); + processRematching(rematchingOption, course, courseMission); + return; + } + processMatchingResult(course, courseMission); + } + + private void processRematching(RematchingOption rematchingOption, Course course, CourseMission courseMission) { + if (rematchingOption.equals(YES)) { + processMatchingResult(course, courseMission); + } + } + + private void processMatchingResult(Course course, CourseMission courseMission) { + Optional nonDuplicatedResult = createNonDuplicatedResult(courseMission, course); + + if (!nonDuplicatedResult.isPresent()) { + outputView.outputErrorMessage(FAIL_TO_MATCH.getMessage()); + } + + processNonDuplicatedResult(courseMission, nonDuplicatedResult); + } + + private void processNonDuplicatedResult(CourseMission courseMission, Optional nonDuplicatedResult) { + MatchingResult matchingResult = nonDuplicatedResult.get(); + outputView.outputPairMatchingResult(MatchingResultMapper.from(matchingResult)); + matchingService.save(courseMission, matchingResult); + } + + private Optional createNonDuplicatedResult(CourseMission courseMission, Course course) { + int count = 0; + + while (count < REMATCHING_COUNT) { + MatchingResult matchingResult = matchingService.createMatchingResult(course.getCrews()); + if (!matchingService.isDuplicatedPair(courseMission,matchingResult)) { + return Optional.ofNullable(matchingResult); + } + } + + return Optional.empty(); + } + + private RematchingOption inputValidRematchingOption() { + return InputUtil.retryOnInvalidInput(this::inputRematchingOption, + errorMessage -> outputView.outputErrorMessage(INVALID_REMATCHING_SELECT.getMessage())); + } + + private RematchingOption inputRematchingOption() { + return inputView.inputRematchingOption(); + } + + private CourseMission inputValidCourseMission() { + return InputUtil.retryOnInvalidInput(this::inputCourseMission, + errorMessage -> outputView.outputErrorMessage(INVALID_COURSE_MISSION.getMessage())); + } + + private CourseMission inputCourseMission() { + return inputView.inputCourseMission(); + } + + private Function inputValidFunction() { + return InputUtil.retryOnInvalidInput(this::inputFunction, + errorMessage -> outputView.outputErrorMessage(INVALID_FUNCTION.getMessage())); + } + + private Function inputFunction() { + return inputView.inputFunction(); + } +} \ No newline at end of file diff --git a/src/main/java/pairmatching/domain/dto/CourseMissionMapper.java b/src/main/java/pairmatching/domain/dto/CourseMissionMapper.java new file mode 100644 index 000000000..7712c35d7 --- /dev/null +++ b/src/main/java/pairmatching/domain/dto/CourseMissionMapper.java @@ -0,0 +1,50 @@ +package pairmatching.domain.dto; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.entity.Course; +import pairmatching.domain.entity.CourseMission; +import pairmatching.domain.entity.Level; +import pairmatching.domain.entity.Mission; +import pairmatching.util.StringUtil; + +public class CourseMissionMapper { + + private CourseMissionMapper() { + + } + + public static CourseMission toCourseMission(String input) { + List separated = seperate(input); + + final String courseName = separated.get(0); + final String levelName = separated.get(1); + final String missionName = separated.get(2); + + Course course = toCourse(courseName); + Level level = toLevel(levelName); + Mission mission = toMission(level, missionName); + + return CourseMission.create(course, level, mission); + } + + private static List seperate(String input) { + String deleteSpaces = StringUtil.removeAllSpaces(input); + List separated = Arrays.stream(deleteSpaces.split(",")) + .collect(Collectors.toList()); //실전에선 toList() 사용 + return separated; + } + + private static Level toLevel(String levelName) { + return Level.findLevel(levelName); + } + + private static Course toCourse(String courseName) { + return Course.findCourse(courseName); + } + + private static Mission toMission(Level level, String missionName) { + return level.findMission(missionName); + } +} diff --git a/src/main/java/pairmatching/domain/dto/FunctionMapper.java b/src/main/java/pairmatching/domain/dto/FunctionMapper.java new file mode 100644 index 000000000..23d754882 --- /dev/null +++ b/src/main/java/pairmatching/domain/dto/FunctionMapper.java @@ -0,0 +1,16 @@ +package pairmatching.domain.dto; + +import pairmatching.domain.entity.Function; +import pairmatching.util.StringUtil; + +public class FunctionMapper { + private FunctionMapper() { + + } + + public static Function toFunction(String input) { + final String deleteSpaces = StringUtil.removeAllSpaces(input); + + return Function.findFunction(deleteSpaces); + } +} diff --git a/src/main/java/pairmatching/domain/dto/MatchingResultDto.java b/src/main/java/pairmatching/domain/dto/MatchingResultDto.java new file mode 100644 index 000000000..96e38ce4c --- /dev/null +++ b/src/main/java/pairmatching/domain/dto/MatchingResultDto.java @@ -0,0 +1,17 @@ +package pairmatching.domain.dto; + +import java.util.List; + +public class MatchingResultDto { + + private final List> matchingResult; + + public MatchingResultDto(List> matchingResult) { + this.matchingResult = matchingResult; + } + + public List> getMatchingResult() { + return matchingResult; + } +} + diff --git a/src/main/java/pairmatching/domain/dto/MatchingResultMapper.java b/src/main/java/pairmatching/domain/dto/MatchingResultMapper.java new file mode 100644 index 000000000..9c8be5d0e --- /dev/null +++ b/src/main/java/pairmatching/domain/dto/MatchingResultMapper.java @@ -0,0 +1,23 @@ +package pairmatching.domain.dto; + +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.entity.MatchingResult; +import pairmatching.domain.entity.Pair; + +public class MatchingResultMapper { + + private MatchingResultMapper() { + } + + public static MatchingResultDto from(MatchingResult matchingResult) { + List pairs = matchingResult.getPairs(); + List> transformed = pairs + .stream() + .map(pair -> pair.getCrews()) + .collect(Collectors.toList()); + + return new MatchingResultDto(transformed); + } + +} diff --git a/src/main/java/pairmatching/domain/dto/RematchingOptionMapper.java b/src/main/java/pairmatching/domain/dto/RematchingOptionMapper.java new file mode 100644 index 000000000..9d894e438 --- /dev/null +++ b/src/main/java/pairmatching/domain/dto/RematchingOptionMapper.java @@ -0,0 +1,9 @@ +package pairmatching.domain.dto; + +import pairmatching.domain.entity.RematchingOption; + +public class RematchingOptionMapper { + public static RematchingOption toRematchingOption(String input) { + return RematchingOption.findRematchingOption(input); + } +} diff --git a/src/main/java/pairmatching/domain/entity/Course.java b/src/main/java/pairmatching/domain/entity/Course.java new file mode 100644 index 000000000..ce437b05f --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/Course.java @@ -0,0 +1,53 @@ +package pairmatching.domain.entity; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import pairmatching.util.ExceptionUtil; +import pairmatching.util.FileUtil; + +public enum Course { + BACKEND("백엔드", "backend-crew.md"), + FRONTEND("프론트엔드", "frontend-crew.md"); + + private final String description; + private final String filePath; + + Course(final String description, final String filePath) { + this.description = description; + this.filePath = filePath; + } + + public static Course findCourse(String courseName) { + return Arrays.stream(values()) + .filter(course -> course.description.equals(courseName)) + .findAny() + .orElseThrow(() -> ExceptionUtil.returnInvalidValueException()); + } + + + public List getCrews() { + try { + return FileUtil.readFileAsList(getFilePath()); + } catch (IOException e) { + throw ExceptionUtil.returnInvalidValueException(); + } catch (URISyntaxException e) { + throw ExceptionUtil.returnInvalidValueException(); + } + } + + public String getDescription() { + return description; + } + + public String getFilePath() throws URISyntaxException { + return Paths.get(getClass() + .getClassLoader() + .getResource(filePath) + .toURI()).toString(); + } + + +} \ No newline at end of file diff --git a/src/main/java/pairmatching/domain/entity/CourseMission.java b/src/main/java/pairmatching/domain/entity/CourseMission.java new file mode 100644 index 000000000..2d5d2436a --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/CourseMission.java @@ -0,0 +1,52 @@ +package pairmatching.domain.entity; + +import java.util.Objects; + +public class CourseMission { + private final Course course; + private final Level level; + private final Mission mission; + + private CourseMission(Course course, Level level, Mission mission) { + this.course = course; + this.level = level; + this.mission = mission; + } + + public static CourseMission create(Course course, Level level, Mission mission) { + return new CourseMission(course, level, mission); + } + + public Course getCourse() { + return course; + } + + public Level getLevel() { + return level; + } + + public Mission getMission() { + return mission; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other == null || getClass() != other.getClass()) { + return false; + } + + CourseMission compared = (CourseMission) other; + return course.equals(compared.getCourse()) && + level.equals(compared.getLevel()) && + mission.equals(compared.getMission()); + } + + @Override + public int hashCode() { + return Objects.hash(course, level, mission); + } +} diff --git a/src/main/java/pairmatching/domain/entity/Function.java b/src/main/java/pairmatching/domain/entity/Function.java new file mode 100644 index 000000000..30f252b61 --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/Function.java @@ -0,0 +1,34 @@ +package pairmatching.domain.entity; + +import java.util.Arrays; +import pairmatching.util.ExceptionUtil; + +public enum Function { + PAIR_MATCHING("1", "페어 매칭"), + PAIR_CHECK("2", "페어 조회"), + PAIR_RESET("3", "페어 초기화"), + QUIT("Q", "종료"); + + private final String option; + private final String description; + + Function(String option, String description) { + this.option = option; + this.description = description; + } + + public static Function findFunction(String option) { + return Arrays.stream(Function.values()) + .filter(function -> function.option.equals(option)) + .findAny() + .orElseThrow(() -> ExceptionUtil.returnInvalidValueException()); + } + + public String getOption() { + return option; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/pairmatching/domain/entity/Level.java b/src/main/java/pairmatching/domain/entity/Level.java new file mode 100644 index 000000000..93744a9d6 --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/Level.java @@ -0,0 +1,45 @@ +package pairmatching.domain.entity; + +import java.util.Arrays; +import java.util.List; +import pairmatching.util.ExceptionUtil; + +public enum Level { + LEVEL1("레벨1", Arrays.asList(Mission.RACING_CAR, Mission.LOTTO, Mission.BASEBALL)), + LEVEL2("레벨2", Arrays.asList(Mission.SHOPPING_CART, Mission.PAYMENT, Mission.SUBWAY_MAP)), + LEVEL3("레벨3", Arrays.asList()), + LEVEL4("레벨4", Arrays.asList(Mission.PERFORMANCE_IMPROVEMENT, Mission.DEPLOYMENT)), + LEVEL5("레벨5", Arrays.asList()); + + private final String description; + private final List missions; + + Level(String description, List missions) { + this.description = description; + this.missions = missions; + } + + public static Level findLevel(String levelName) { + return Arrays.stream(values()) + .filter(level -> level.description.equals(levelName)) + .findAny() + .orElseThrow(() -> ExceptionUtil.returnInvalidValueException()); + } + + public Mission findMission(String missionName) { + return this.missions + .stream() + .filter(mission -> mission.getDescription().equals(missionName)) + .findAny() + .orElseThrow(() -> ExceptionUtil.returnInvalidValueException()); + } + + public String getDescription() { + return description; + } + + public List getMissions() { + return missions; + } + +} \ No newline at end of file diff --git a/src/main/java/pairmatching/domain/entity/MatchingResult.java b/src/main/java/pairmatching/domain/entity/MatchingResult.java new file mode 100644 index 000000000..a03cb861e --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/MatchingResult.java @@ -0,0 +1,19 @@ +package pairmatching.domain.entity; + +import java.util.List; + +public class MatchingResult { + private final List pairs; + + private MatchingResult(List pairs) { + this.pairs = pairs; + } + + public static MatchingResult create(List pairs) { + return new MatchingResult(pairs); + } + + public List getPairs() { + return pairs; + } +} diff --git a/src/main/java/pairmatching/domain/entity/Mission.java b/src/main/java/pairmatching/domain/entity/Mission.java new file mode 100644 index 000000000..e26f21ae3 --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/Mission.java @@ -0,0 +1,22 @@ +package pairmatching.domain.entity; + +public enum Mission { + RACING_CAR("자동차경주"), + LOTTO("로또"), + BASEBALL("숫자야구게임"), + SHOPPING_CART("장바구니"), + PAYMENT("결제"), + SUBWAY_MAP("지하철노선도"), + PERFORMANCE_IMPROVEMENT("성능개선"), + DEPLOYMENT("배포"); + + private final String description; + + Mission(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/pairmatching/domain/entity/Pair.java b/src/main/java/pairmatching/domain/entity/Pair.java new file mode 100644 index 000000000..cb2b5068b --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/Pair.java @@ -0,0 +1,45 @@ +package pairmatching.domain.entity; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Pair { + private final List crews; + + private Pair(List crews) { + Collections.sort(crews); + this.crews = crews; + } + + public static Pair create(List crews) { + return new Pair(crews); + } + + public List getCrews() { + return crews; + } + + public void addCrew(String crew) { + crews.add(crew); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + Pair otherPair = (Pair) other; + + return otherPair.getCrews().equals(crews); + } + + @Override + public int hashCode() { + return Objects.hash(crews); + } +} diff --git a/src/main/java/pairmatching/domain/entity/RematchingOption.java b/src/main/java/pairmatching/domain/entity/RematchingOption.java new file mode 100644 index 000000000..231367e71 --- /dev/null +++ b/src/main/java/pairmatching/domain/entity/RematchingOption.java @@ -0,0 +1,26 @@ +package pairmatching.domain.entity; + +import java.util.Arrays; +import pairmatching.util.ExceptionUtil; + +public enum RematchingOption { + YES("네"), + NO("아니오"); + + private final String description; + + RematchingOption(String description) { + this.description = description; + } + + public static RematchingOption findRematchingOption(String input) { + return Arrays.stream(values()) + .filter(rematchingOption -> rematchingOption.description.equals(input)) + .findAny() + .orElseThrow(() -> ExceptionUtil.returnInvalidValueException()); + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/pairmatching/messages/ErrorMessages.java b/src/main/java/pairmatching/messages/ErrorMessages.java new file mode 100644 index 000000000..6e8c78734 --- /dev/null +++ b/src/main/java/pairmatching/messages/ErrorMessages.java @@ -0,0 +1,21 @@ +package pairmatching.messages; + +public enum ErrorMessages { + + INVALID_FUNCTION("타당하지 않은 기능입니다."), + INVALID_COURSE_MISSION("타당하지 않은 과정, 레벨, 미션입니다."), + NOT_EXIST_MATCHING_RESULT("매칭 이력이 없습니다."), + INVALID_REMATCHING_SELECT("타당하지 않은 선택입니다."), + FAIL_TO_MATCH("매칭 실패"); + + private static final String prefix = "[ERROR] "; + private final String message; + + ErrorMessages(final String message) { + this.message = prefix + message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/pairmatching/messages/IOMessages.java b/src/main/java/pairmatching/messages/IOMessages.java new file mode 100644 index 000000000..aacbd4316 --- /dev/null +++ b/src/main/java/pairmatching/messages/IOMessages.java @@ -0,0 +1,27 @@ +package pairmatching.messages; + +public enum IOMessages { + + COURSE("과정"), + LEVEL("레벨"), + MISSION("미션"), + INPUT_SELECT_OPTIONS( + String.format("%s, %s, %s을 선택하세요.", COURSE.getMessage(), LEVEL.getMessage(), MISSION.getMessage())), + SELECT_EXAMPLE("ex) 백엔드, 레벨1, 자동차경주"), + INPUT_FUNCTION("기능을 선택하세요."), + INPUT_REMATCHING("매칭 정보가 있습니다. 다시 매칭하시겠습니까?"), + OUTPUT_DIVIDING_LINE("#############################################"), + OUTPUT_PAIRMATCHING_RESULT("페어 매칭 결과입니다."), + OUTPUT_RESET("초기화 되었습니다."); + + + private final String message; + + IOMessages(final String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/pairmatching/repository/MatchingResultRepository.java b/src/main/java/pairmatching/repository/MatchingResultRepository.java new file mode 100644 index 000000000..946a80750 --- /dev/null +++ b/src/main/java/pairmatching/repository/MatchingResultRepository.java @@ -0,0 +1,34 @@ +package pairmatching.repository; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import pairmatching.domain.entity.CourseMission; +import pairmatching.domain.entity.MatchingResult; +import pairmatching.domain.entity.Mission; + +public class MatchingResultRepository { + + protected final Map store = new LinkedHashMap<>(); + + public void save(CourseMission courseMission, MatchingResult matchingResult) { + store.put(courseMission, matchingResult); + } + + public Optional findBySameObject(CourseMission courseMission) { + return Optional.ofNullable(store.get(courseMission)); + } + + public Optional findByMission(Mission mission) { + return store.keySet() + .stream() + .filter(courseMission -> courseMission.getMission().equals(mission)) + .map(courseMission -> store.get(courseMission)) + .findAny(); + } + + public void deleteAll() { + store.clear(); + } + +} diff --git a/src/main/java/pairmatching/service/MatchingService.java b/src/main/java/pairmatching/service/MatchingService.java new file mode 100644 index 000000000..7c1a5068f --- /dev/null +++ b/src/main/java/pairmatching/service/MatchingService.java @@ -0,0 +1,83 @@ +package pairmatching.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import pairmatching.domain.entity.CourseMission; +import pairmatching.domain.entity.MatchingResult; +import pairmatching.domain.entity.Mission; +import pairmatching.domain.entity.Pair; +import pairmatching.repository.MatchingResultRepository; +import pairmatching.util.MathUtil; +import pairmatching.util.RandonUtil; + +public class MatchingService { + + private final MatchingResultRepository matchingResultRepository; + + public MatchingService(MatchingResultRepository matchingResultRepository) { + this.matchingResultRepository = matchingResultRepository; + } + + public MatchingResult createMatchingResult(List crews) { + final List shuffledCrews = RandonUtil.shuffle(crews); + final List pairs = new ArrayList<>(); + final int crewSize = shuffledCrews.size(); + final int pairSize = crewSize / 2; + + for (int pairStartIndex = 0; pairStartIndex < crewSize; pairStartIndex += 2) { + pairs.add(createPair(shuffledCrews, pairStartIndex)); + } + + processLastCrew(crewSize, shuffledCrews, pairs, pairSize); + return MatchingResult.create(pairs); + } + + private static void processLastCrew(int crewSize, List shuffledCrews, List pairs, int pairSize) { + if (MathUtil.isOddNumber(crewSize)) { + String lastCrew = shuffledCrews.get(crewSize - 1); + pairs.get(pairSize - 1).addCrew(lastCrew); + } + } + + public Optional findMatchingResult(CourseMission courseMission) { + return matchingResultRepository.findBySameObject(courseMission); + } + + public void resetMatching() { + matchingResultRepository.deleteAll(); + } + + public void save(CourseMission courseMission, MatchingResult matchingResult) { + matchingResultRepository.save(courseMission, matchingResult); + } + + public boolean isDuplicatedPair(CourseMission courseMission, MatchingResult matchingResult) { + List missions = courseMission.getLevel().getMissions(); + List pairs = matchingResult.getPairs(); + + return missions.stream() + .allMatch(mission -> { + Optional foundMatchingResult + = matchingResultRepository.findByMission(mission); + + if (!foundMatchingResult.isPresent()) { + return false; + } + + List foundPairs = foundMatchingResult.get().getPairs(); + return foundPairs.stream() + .allMatch(pair -> pairs.contains(pair)); + }); + } + + private Pair createPair(final List shuffledCrews, final int index) { + List pairMembers = new ArrayList<>(); + pairMembers.add(shuffledCrews.get(index)); + pairMembers.add(shuffledCrews.get(index + 1)); + + return Pair.create(pairMembers); + } + +} + diff --git a/src/main/java/pairmatching/util/ExceptionUtil.java b/src/main/java/pairmatching/util/ExceptionUtil.java new file mode 100644 index 000000000..4c09c027a --- /dev/null +++ b/src/main/java/pairmatching/util/ExceptionUtil.java @@ -0,0 +1,29 @@ +package pairmatching.util; + +public final class ExceptionUtil { + + private ExceptionUtil() { + + } + + public static void throwInvalidValueException() { + + throw new IllegalArgumentException(); + } + + public static IllegalArgumentException returnInvalidValueException() { + + return new IllegalArgumentException(); + } + + public static void throwInvalidValueException(final String message) { + + throw new IllegalArgumentException(message); + } + + public static IllegalArgumentException returnInvalidValueException(final String message) { + + return new IllegalArgumentException(message); + } + +} diff --git a/src/main/java/pairmatching/util/FileUtil.java b/src/main/java/pairmatching/util/FileUtil.java new file mode 100644 index 000000000..905673b9d --- /dev/null +++ b/src/main/java/pairmatching/util/FileUtil.java @@ -0,0 +1,17 @@ +package pairmatching.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; + +public class FileUtil { + + private FileUtil() { + + } + + public static List readFileAsList(String filePath) throws IOException { + return Files.readAllLines(Paths.get(filePath)); + } +} diff --git a/src/main/java/pairmatching/util/InputUtil.java b/src/main/java/pairmatching/util/InputUtil.java new file mode 100644 index 000000000..287847ec6 --- /dev/null +++ b/src/main/java/pairmatching/util/InputUtil.java @@ -0,0 +1,27 @@ +package pairmatching.util; + +import camp.nextstep.edu.missionutils.Console; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public final class InputUtil { + + private InputUtil() { + + } + + public static String input() { + return Console.readLine(); + } + + public static T retryOnInvalidInput(Supplier inputSupplier, + Consumer errorHandler) { + while (true) { + try { + return inputSupplier.get(); + } catch (IllegalArgumentException e) { + errorHandler.accept(e.getMessage()); + } + } + } +} diff --git a/src/main/java/pairmatching/util/MathUtil.java b/src/main/java/pairmatching/util/MathUtil.java new file mode 100644 index 000000000..4987f7509 --- /dev/null +++ b/src/main/java/pairmatching/util/MathUtil.java @@ -0,0 +1,10 @@ +package pairmatching.util; + +public class MathUtil { + private MathUtil() { + } + + public static boolean isOddNumber(final int size) { + return size % 2 == 1; + } +} diff --git a/src/main/java/pairmatching/util/RandonUtil.java b/src/main/java/pairmatching/util/RandonUtil.java new file mode 100644 index 000000000..17f6d4828 --- /dev/null +++ b/src/main/java/pairmatching/util/RandonUtil.java @@ -0,0 +1,15 @@ +package pairmatching.util; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.List; + +public class RandonUtil { + + private RandonUtil() { + + } + + public static List shuffle(List elements) { + return Randoms.shuffle(elements); + } +} diff --git a/src/main/java/pairmatching/util/StringUtil.java b/src/main/java/pairmatching/util/StringUtil.java new file mode 100644 index 000000000..2e26760de --- /dev/null +++ b/src/main/java/pairmatching/util/StringUtil.java @@ -0,0 +1,21 @@ +package pairmatching.util; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public final class StringUtil { + + private StringUtil() { + + } + + public static String removeAllSpaces(final String input) { + return input.replace(" ", ""); + } + + public static String joinNonEmptyStrings(String delimiter, String... parts) { + return Arrays.stream(parts) + .filter(part -> !part.isEmpty()) + .collect(Collectors.joining(delimiter)); + } +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 000000000..d22afae75 --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,49 @@ +package pairmatching.view; + +import static pairmatching.messages.IOMessages.INPUT_FUNCTION; +import static pairmatching.messages.IOMessages.INPUT_REMATCHING; +import static pairmatching.messages.IOMessages.INPUT_SELECT_OPTIONS; +import static pairmatching.messages.IOMessages.SELECT_EXAMPLE; + +import java.util.Arrays; +import pairmatching.domain.dto.CourseMissionMapper; +import pairmatching.domain.dto.FunctionMapper; +import pairmatching.domain.dto.RematchingOptionMapper; +import pairmatching.domain.entity.CourseMission; +import pairmatching.domain.entity.Function; +import pairmatching.domain.entity.RematchingOption; +import pairmatching.util.InputUtil; + +public class InputView { + + private static void outputFunctions() { + Arrays.stream(Function.values()) + .forEach(function + -> System.out.println(function.getOption() + ". " + function.getDescription())); + } + + public Function inputFunction() { + System.out.println(INPUT_FUNCTION.getMessage()); + outputFunctions(); + + String input = InputUtil.input(); + + return FunctionMapper.toFunction(input); + } + + public CourseMission inputCourseMission() { + System.out.println(INPUT_SELECT_OPTIONS.getMessage()); + System.out.println(SELECT_EXAMPLE.getMessage()); + String input = InputUtil.input(); + + return CourseMissionMapper.toCourseMission(input); + } + + public RematchingOption inputRematchingOption() { + System.out.println(INPUT_REMATCHING.getMessage()); + System.out.println(RematchingOption.YES.getDescription() + " | " + RematchingOption.NO.getDescription()); + String input = InputUtil.input(); + + return RematchingOptionMapper.toRematchingOption(input); + } +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 000000000..3e1219e49 --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,71 @@ +package pairmatching.view; + +import static pairmatching.messages.IOMessages.COURSE; +import static pairmatching.messages.IOMessages.MISSION; +import static pairmatching.messages.IOMessages.OUTPUT_DIVIDING_LINE; +import static pairmatching.messages.IOMessages.OUTPUT_PAIRMATCHING_RESULT; +import static pairmatching.messages.IOMessages.OUTPUT_RESET; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.domain.dto.MatchingResultDto; +import pairmatching.domain.entity.Course; +import pairmatching.domain.entity.Level; +import pairmatching.domain.entity.Mission; + +public class OutputView { + + public void outputCourseMission() { + System.out.println(OUTPUT_DIVIDING_LINE.getMessage()); + + outputCourses(); + outputMissions(); + + System.out.println(OUTPUT_DIVIDING_LINE.getMessage()); + } + + public void outputPairMatchingResult(MatchingResultDto matchingResultDto) { + System.out.println(OUTPUT_PAIRMATCHING_RESULT.getMessage()); + + List> matchings = matchingResultDto.getMatchingResult(); + matchings.stream() + .map(this::formatPair) + .forEach(System.out::println); + } + + public void outputErrorMessage(String message) { + System.out.println(message); + } + + public void ouputReset() { + System.out.println(OUTPUT_RESET.getMessage()); + } + private String formatPair(List list) { + return String.join(" : ", list); + } + + private void outputCourses() { + String courses = Arrays.stream(Course.values()) + .map(Course::getDescription) + .collect(Collectors.joining(" | ")); + System.out.println(COURSE.getMessage() + ": " + courses); + } + + private void outputMissions() { + System.out.println(MISSION.getMessage() + ": "); + Arrays.stream(Level.values()) + .map(this::formatMissionsForLevel) + .forEach(System.out::println); + } + + private String formatMissionsForLevel(Level level) { + String missions = level.getMissions() + .stream() + .map(Mission::getDescription) + .collect(Collectors.joining(" | ")); + + return " - " + level.getDescription() + ": " + missions; + } + +} diff --git a/src/test/java/pairmatching/ApplicationTest.java b/src/test/java/pairmatching/ApplicationTest.java index 2dff1e6d3..e24e6eb1f 100644 --- a/src/test/java/pairmatching/ApplicationTest.java +++ b/src/test/java/pairmatching/ApplicationTest.java @@ -7,7 +7,6 @@ import camp.nextstep.edu.missionutils.test.NsTest; import java.util.Arrays; import org.junit.jupiter.api.Test; -import pairmatching.Application; class ApplicationTest extends NsTest { @@ -16,21 +15,21 @@ class ApplicationTest extends NsTest { @Test void 짝수_인원_페어_매칭() { assertShuffleTest( - () -> { - run("1", "백엔드, 레벨1, 자동차경주", "Q"); - assertThat(output()).contains("태웅 : 백호", "치수 : 태섭"); - }, - Arrays.asList("태웅", "백호", "치수", "태섭") + () -> { + run("1", "백엔드, 레벨1, 자동차경주", "Q"); + assertThat(output()).contains("태웅 : 백호", "치수 : 태섭"); + }, + Arrays.asList("태웅", "백호", "치수", "태섭") ); } @Test void 없는_미션에_대한_예외_처리() { assertSimpleTest( - () -> { - runException("1", "백엔드, 레벨1, 오징어게임"); - assertThat(output()).contains(ERROR_MESSAGE); - } + () -> { + runException("1", "백엔드, 레벨1, 오징어게임"); + assertThat(output()).contains(ERROR_MESSAGE); + } ); } diff --git a/src/test/java/pairmatching/domain/dto/CourseMissionMapperTest.java b/src/test/java/pairmatching/domain/dto/CourseMissionMapperTest.java new file mode 100644 index 000000000..98caaeece --- /dev/null +++ b/src/test/java/pairmatching/domain/dto/CourseMissionMapperTest.java @@ -0,0 +1,44 @@ +package pairmatching.domain.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import pairmatching.domain.entity.Course; +import pairmatching.domain.entity.CourseMission; +import pairmatching.domain.entity.Level; +import pairmatching.domain.entity.Mission; + +public class CourseMissionMapperTest { + + private static Stream provideInputForCourseMission() { + return Stream.of( + Arguments.of( + "백엔드, 레벨1, 자동차경주", + Course.BACKEND, + Level.LEVEL1, + Mission.RACING_CAR), + Arguments.of("프론트엔드, 레벨2, 장바구니", + Course.FRONTEND, + Level.LEVEL2, + Mission.SHOPPING_CART) + ); + } + + @ParameterizedTest + @MethodSource("provideInputForCourseMission") + public void testToCourseMission(String input, + Course expectedCourse, + Level expectedLevel, + Mission expectedMission) { + CourseMission courseMission = CourseMissionMapper.toCourseMission(input); + + assertNotNull(courseMission); + assertEquals(expectedCourse, courseMission.getCourse()); + assertEquals(expectedLevel, courseMission.getLevel()); + assertEquals(expectedMission, courseMission.getMission()); + } +} diff --git a/src/test/java/pairmatching/domain/dto/MatchingResultMapperTest.java b/src/test/java/pairmatching/domain/dto/MatchingResultMapperTest.java new file mode 100644 index 000000000..18c363824 --- /dev/null +++ b/src/test/java/pairmatching/domain/dto/MatchingResultMapperTest.java @@ -0,0 +1,34 @@ +package pairmatching.domain.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import pairmatching.domain.entity.MatchingResult; +import pairmatching.domain.entity.Pair; + +public class MatchingResultMapperTest { + private static Stream 제공_매칭결과와_예상결과() { + return Stream.of( + Arguments.of( + MatchingResult.create(Arrays.asList(Pair.create(Arrays.asList("크루1", "크루2")))), + Arrays.asList(Arrays.asList("크루1", "크루2")) + ) + ); + } + + + @ParameterizedTest + @MethodSource("제공_매칭결과와_예상결과") + void 매칭결과_DTO_변환_테스트(MatchingResult matchingResult, List> expected) { + // When: 실행 + List> actual = MatchingResultMapper.from(matchingResult).getMatchingResult(); + + // Then: 검증 + assertEquals(expected, actual); + } +} diff --git a/src/test/java/pairmatching/domain/entity/CourseMissionTest.java b/src/test/java/pairmatching/domain/entity/CourseMissionTest.java new file mode 100644 index 000000000..2758d5f4d --- /dev/null +++ b/src/test/java/pairmatching/domain/entity/CourseMissionTest.java @@ -0,0 +1,46 @@ +package pairmatching.domain.entity; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CourseMissionTest { + + private static Stream 제공_코스미션_비교_데이터() { + return Stream.of( + Arguments.of( + CourseMission.create(Course.BACKEND, Level.LEVEL1, Mission.RACING_CAR), + CourseMission.create(Course.BACKEND, Level.LEVEL1, Mission.RACING_CAR), + true + ), + Arguments.of( + CourseMission.create(Course.BACKEND, Level.LEVEL1, Mission.RACING_CAR), + CourseMission.create(Course.BACKEND, Level.LEVEL2, Mission.SHOPPING_CART), + false + ), + Arguments.of( + CourseMission.create(Course.BACKEND, Level.LEVEL1, Mission.RACING_CAR), + new Object(), + false + ), + Arguments.of( + CourseMission.create(Course.BACKEND, Level.LEVEL1, Mission.RACING_CAR), + null, + false + ) + ); + } + + @ParameterizedTest + @MethodSource("제공_코스미션_비교_데이터") + void 코스미션_동등성_테스트(CourseMission mission1, Object mission2, boolean expected) { + // When: 실행 + boolean result = mission1.equals(mission2); + + // Then: 검증 + assertEquals(expected, result); + } +} diff --git a/src/test/java/pairmatching/domain/entity/CourseTest.java b/src/test/java/pairmatching/domain/entity/CourseTest.java new file mode 100644 index 000000000..f9919b7c1 --- /dev/null +++ b/src/test/java/pairmatching/domain/entity/CourseTest.java @@ -0,0 +1,61 @@ +package pairmatching.domain.entity; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class CourseTest { + + private static Stream 코스이름_및_예상코스() { + return Stream.of( + Arguments.of("백엔드", Course.BACKEND, 20), + Arguments.of("프론트엔드", Course.FRONTEND, 15) + ); + } + + private static Stream provideInvalidCourseNames() { + return Stream.of("잘못된코스", "존재하지않음", "비어있음"); + } + + @ParameterizedTest + @MethodSource("코스이름_및_예상코스") + public void 주어진_코스이름으로_코스찾기_테스트(String courseName, Course expectedCourse) { + // when + Course result = Course.findCourse(courseName); + + // then + assertEquals(expectedCourse, result); + } + + @ParameterizedTest + @MethodSource("코스이름_및_예상코스") + public void 주어진_코스의_크루목록_가져오기_테스트(String courseName, Course course, int size) { + // when + List crews = course.getCrews(); + + // then + assertNotNull(crews); + assertThat(crews.size()).isEqualTo(size); + } + + @ParameterizedTest + @MethodSource("코스이름_및_예상코스") + void 유효한_코스_찾기_테스트(String courseName, Course expectedCourse) { + Course actualCourse = Course.findCourse(courseName); + assertEquals(expectedCourse, actualCourse); + } + + @ParameterizedTest + @MethodSource("provideInvalidCourseNames") + void 잘못된_코스_찾기_예외_테스트(String courseName) { + assertThatThrownBy(() -> Course.findCourse(courseName)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/pairmatching/service/MatchingServiceTest.java b/src/test/java/pairmatching/service/MatchingServiceTest.java new file mode 100644 index 000000000..d0a9318d1 --- /dev/null +++ b/src/test/java/pairmatching/service/MatchingServiceTest.java @@ -0,0 +1,39 @@ +package pairmatching.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import pairmatching.domain.entity.MatchingResult; +import pairmatching.repository.MatchingResultRepository; + +public class MatchingServiceTest { + + private static Stream 제공_크루목록과_예상_매칭결과() { + return Stream.of( + Arguments.of(Arrays.asList("크루1", "크루2"), 1), // 2명의 크루, 1개의 페어 예상 + Arguments.of(Arrays.asList("크루1", "크루2", "크루3"), 1), // 3명의 크루, 1개의 페어 예상 (3인 페어) + Arguments.of(Arrays.asList("크루1", "크루2", "크루3", "크루4"), 2), // 4명의 크루, 2개의 페어 예상 + Arguments.of(Arrays.asList("크루1", "크루2", "크루3", "크루4", "크루5"), 2), // 5명의 크루, 3개의 페어 예상 + Arguments.of(Arrays.asList( + "크루1", "크루2", "크루3", "크루4", "크루5", "크루6"), 3) // 6명의 크루, 3개의 페어 예상 + ); + } + + @ParameterizedTest + @MethodSource("제공_크루목록과_예상_매칭결과") + void 매칭결과_생성_테스트(List crews, int expectedNumberOfPairs) { + MatchingService matchingService = new MatchingService(new MatchingResultRepository()); + // When: 실행 + MatchingResult matchingResult = matchingService.createMatchingResult(crews); + + // Then: 검증 + assertNotNull(matchingResult); + assertEquals(expectedNumberOfPairs, matchingResult.getPairs().size()); + } +} diff --git a/src/test/java/pairmatching/util/FileUtilTest.java b/src/test/java/pairmatching/util/FileUtilTest.java new file mode 100644 index 000000000..822bb2752 --- /dev/null +++ b/src/test/java/pairmatching/util/FileUtilTest.java @@ -0,0 +1,38 @@ +package pairmatching.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class FileUtilTest { + + private static Stream provideLinesForTest() { + return Stream.of( + Arguments.of(0, "백호"), Arguments.of(1, "태웅"), Arguments.of(2, "치수"), + Arguments.of(3, "태섭"), Arguments.of(4, "대만"), Arguments.of(5, "준호"), + Arguments.of(6, "대협"), Arguments.of(7, "덕규"), Arguments.of(8, "태산"), + Arguments.of(9, "경태"), Arguments.of(10, "수겸"), Arguments.of(11, "현준"), + Arguments.of(12, "준섭"), Arguments.of(13, "한나"), Arguments.of(14, "소연"), + Arguments.of(15, "호열"), Arguments.of(16, "대남"), Arguments.of(17, "용팔"), + Arguments.of(18, "구식"), Arguments.of(19, "달재") + ); + } + + @ParameterizedTest + @MethodSource("provideLinesForTest") + public void testFileContent(int lineIndex, String expectedLine) throws IOException, URISyntaxException { + final String filePath + = Paths.get(getClass().getClassLoader().getResource("backend-crew.md").toURI()).toString(); + final List members = FileUtil.readFileAsList(filePath); + final String result = members.get(lineIndex); + + assertEquals(expectedLine, result, "Mismatch at line " + (lineIndex + 1)); + } +}