로또 구입 금액을 입력받고 금액에 맞는 로또를 발행하고, 당첨번호와 비교해 최종 결과를 출력해야한다.
1. 구현할 기능 목록 작성
구현해야하는 기능을 아래와 같이 정리하였다.
1주차, 2주차 과제와는 달리 에러가 발생하면 에러 메세지 출력 후 해당 부분부터 다시 입력을 받는 것으로 변경되었다.
2. 구조 설계
이번에는 에러 처리를 좀 더 일관적으로 처리하기 위해 BusinessLogicException 클래스를 정의하고,
ExceptionMessage를 Enum 타입으로 정리했다.
또한 로또 당첨여부 확인, 메세지 출력, 수익 계산을 위해 LottoRank에 대한 내용을 Enum 타입으로 정의했다.
3. 비즈니스 로직 코드 작성
1차, 2차 과제 요구사항에 추가로 이번에는 메서드 길이가 15라인을 넘지 않도록 하고, Enum을 적용해야했다.
세부 비즈니스 로직은 아래 링크에 기술되어있다.
3차 과제 코드: (링크)
* 부족한 부분이 많은 코드입니다. 코드 리뷰는 언제든지 환영합니다.^^
4. 테스트 코드 작성
실질적인 기능을 담당하는 model(domain)부분의 테스트 코드를 작성했다.
그런데 클래스 내부에서 사용되는 private 메서드는 테스트 클래스에서 사용할 수 없다는 부분이 고민이었다.
해당 메서드들도 테스트를 해야하는지, 혹은 public 메서드에 대해서만 테스트를 하면 될지 판단하기 어려웠다.
간단히 생각해보면 public 메서드만 테스트해도 정상적으로 구현되었는지 확인할 수 있을것이다.
그래서 이번에는 public 메서드만 테스트하기로 했다.
(private 메서드도 Reflection을 통해 호출이 가능하다고는 한다. 하지만 이 방식은 오류 발생 가능성이 커서 추천하지는 않는다고 한다.)
그리고, 한가지 기능에 대해 여러개의 값(경우)을 넣어 테스트를 해야하는 경우가 있다.
특히 이번 과제의 경우 로또 당첨 번호에 따른 결과가 이에 해당됐는데,
각 케이스별로 테스트 코드를 만들 경우 코드가 중복되고 지나치게 길어지게 되었다. 이 때 사용가능한것이 @ParameterizedTest였다.
@ParameterizedTest는 매개변수화된 테스트에 사용된다. 동일한 테스트에서 여러 입력값을 넣어 반복 수행할 수 있다.
이 때 테스트에 사용될 데이터(매개변수)는 @ValueSource, @CsvSource 등을 사용해 입력해줄 수 있다.
이번 과제에서 사용한 것은 @CsvSource였다.
여러개의 테스트 케이스를 넣어줄 수 있고, delimiter를 직접 지정할 수 있어 [1,2,3,4,5,6]과 같은 데이터를 넣어줄 수 있었다.
@DisplayName("당첨 번호, 보너스 번호 매칭 여부에 따라 적절한 순위를 반환한다.")
@ParameterizedTest
@CsvSource(value = {
"1,2,3,4,5,6:7:RANK1",
"1,2,3,4,5,7:6:RANK2",
"1,2,3,4,5,7:8:RANK3",
"1,2,3,4,7,8:5:RANK4",
"1,2,3,7,8,9:10:RANK5",
"1,2,7,8,9,10:3:NONE"
}, delimiter = ':')
void testDetermineLottoRank(String winningNumbers, int bonusNumber, LottoRank expectedRank) {
Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6));
List<Integer> changedWinningNumbers = Arrays.stream(winningNumbers.split(","))
.map(Integer::parseInt)
.toList();
LottoRank rank = lottoManager.determineLottoRank(lotto, changedWinningNumbers, bonusNumber);
assertEquals(expectedRank, rank);
}
[3차 과제 회고]
내가 어떤 부분이 부족한지 생각해볼 수 있는 과제였다.
각 도메인의 역할을 어떻게 나누어야할지, 해당 도메인의 책임은 어디까지인지,
도메인간의 관계는 어떻게 되는지 등 기본적인 설계 부분에 있어서도 고민이 정말 많이 필요하다는것을 느꼈다.
또한 TDD 방식에 대해서도 다시한번 생각해보게 됐다. TDD하면 마냥 어려운 방식이라고만 생각했는데, 이 생각이 많이 바뀌었다.
코드를 먼저 구현하고 테스트 코드를 작성하려다보니 반복문이나 private 메서드 등으로 인해 어려운 경우들이 있었는데
"테스트가 어려운 코드 유지보수하기에 어려운 코드가 아닐까?" 하는 생각이 들었다.
유지보수나 확장 가능성을 고려하면서 코드를 작성해야겠다는 다짐을 했다.
(물론 테스트 코드를 작성하는 방식에 대한 공부도 아주 많이 필요할것같다.)
이 부분은 TDD 방식을 사용했더라면 좀 더 원활하게 구현할 수 있었을것같다.
이번 과제를 하면서 앞으로 학습해야할 내용들과 공부 방향에 대해 다시한번 정리할 수 있었다.
시간이 조금 걸리더라도 클린 아키텍처와 테스트에 관한 교재를 정독해보고자한다.
부족한 부분이 많지만, 매일매일 하나씩 새로운것들을 배우면서 발전해나가고싶다.
참고자료
객체에서 의존하는 객체가 너무 많을 때는 DIP, DI, IoC Container을 적용하자
'우아한테크코스 프리코스' 카테고리의 다른 글
[우아한테크코스 6기] 최종 코딩테스트: 비상 근무표 작성 (0) | 2023.12.17 |
---|---|
[우아한테크코스 6기] 프리코스 4차 과제: 크리스마스 프로모션(구조 설계, 테스트 커버리지, 최종 회고) (0) | 2023.11.15 |
[우아한테크코스 6기] 프리코스 2차 과제: 레이싱 게임(JUnit5, System.setIn, System.setOut) (0) | 2023.10.31 |
[우아한테크코스] 프리코스 1차 과제: 숫자 야구 (0) | 2023.10.23 |
[우아한테크코스] 프리코스 시작 (환경 설정) (0) | 2023.10.19 |