Search
3️⃣

프리코스 3 주차 테스트 코드 작성기

태그
프리코스
이전글
다음글

도메인 단위로 테스트 작성하기

도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
단위 테스트 작성이 익숙하지 않다면 test/java/lotto/LottoTest를 참고하여 학습한 후 테스트를 구현한다.
도메인 단위로 테스트 코드를 작성한다는 것은, 소프트웨어의 비즈니스 로직이나 애플리케이션의 핵심 기능(도메인 로직)을 집중적으로 테스트하는 것을 의미합니다

그래서 어떤걸 테스트 할까?

제가 짠 기능중에 제일 중요한 기능인 아래 세가지 기능을 테스트 해보기로 하였습니다.

당첨 검증기 테스트

사용자의 로또 번호와 당첨 번호를 비교하여 일치하는 숫자를 제대로 계산 하는지 테스트 합니다.
보너스 번호 일치 여부가 올바르게 확인 되는지 테스트 합니다.

수익률 계산기 테스트

수익률이 정확하게 계산되는지 테스트합니다.
당첨금액이 투자금액보다 높을 때
당첨금액이 투자금액보다 낮을 때

입력값 검증기 테스트

사용자가 입력한 값들이 예상값과 일치하는지 테스트 합니다.

구입금액

숫자 외의 문자가 들어왔을 때 예외를 반환하는지 테스트 합니다.
1000 원 이하일 때 예외를 반환하는지 테스트 합니다.

당첨로또

당첨 로또의 사이즈가 6 이 아닐 때 예외를 반환하는지 테스트 합니다.
당첨 로또 숫자에 중복이 있는 경우 예외를 반환하는지 테스트 합니다.
당첨 로또에 숫자 외의 문자가 들어왔을 때 예외를 반환하는지 테스트 합니다.
당첨 로또의 숫자가 1 이상 45 이하인지 테스트 합니다.

보너스 숫자

보너스 숫자에 숫자 외의 문자가 들어왔을 때 예외를 반환하는지 테스트 합니다.
보너스 숫자가 1 이상 45 이하인지 테스트 합니다.
당첨 로또에 보너스 숫자가 포함되어 있다면 예외를 반환하는지 테스트 합니다.

UnsupportedOperationException

요구사항을 자세히 살펴보면 이러한 요구사항이 있습니다.
로또 번호는 오름차순으로 정렬하여 보여준다.
Markdown
복사
랜덤 로또는 로또 클래스로 받았기 때문에 아래와 같이 작성한 상태였죠.
class LottoGenerator { public List<Lotto> generateLotto(int lottoAmount) { for (int i = 0; i < lottoAmount; i ++) { List<Integer> numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6); Collections.sort(numbers); Lotto lotto = new Lotto(numbers); lottos.add(lotto); } ...
Java
복사
하지만 이렇게 작성하고 테스트를 돌려본 결과 이전과 다르게 UnsupportedOperationException 이 나타났습니다. 처음보는 에러  이 예외는 무엇을 뜻하는 걸까요?

UnsupportedOperationException

java.lang.UnsupportedOperationException at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142) at java.base/java.util.ImmutableCollections$AbstractImmutableList.sort(ImmutableCollections.java:261) at java.base/java.util.Collections.sort(Collections.java:145) at lotto.model.LottoGenerator.generateLotto(LottoGenerator.java:15)
Markdown
복사
에러로그를 통해 알수있는 점은 Java 의 ImmutableCollections 클래스에서 UnsupportedOperationException 이 발생했다는 것입니다  
이는 변경 불가능한 컬렉션(immutable collection)에 대해 sort 와 같은 변경을 시도할 때 나타난다고 합니다. 조금 더 살펴보면 LottoGenerator.generateLotto 메서드의 15번째 줄에서 Collections.sort(numbers) 를 호출하려 할 때 예외가 발생했습니다.
즉 이를 통해 numbers 가 변경 불가능한 리스트임을 알 수 있습니다.

어떻게 해결 했나요?

이 문제를 해결하기 위해 Randoms.pickUniqueNumbersInRange 에서 반환된 리스트를 변경 가능한 새 리스트로 복사한 후에 정렬하였습니다.
for (int i = 0; i < lottoAmount; i ++) { List<Integer> numbers = new ArrayList<>(Randoms.pickUniqueNumbersInRange(1, 45, 6)); Collections.sort(numbers); Lotto lotto = new Lotto(numbers); lottos.add(lotto); }
Java
복사
이렇게 수정하고 테스트를 하니 기능 테스트가 통과되었습니다
하지만 이게 맞는 방법인지는 모르겠어서, 코드 리뷰 후에 다시 생각해보려 합니다.

Private 메서드 테스트

이전에 작성한 JUnit5 와 AssertJ 로 테스트 코드 작성하기 를 보면 Private 메서드를 억지로 테스트함이 없지 않아 있었습니다. 사실 이렇게 하면 안되고, public 메서드를 통해 간접적으로 테스트를 해야함이 옳습니다. 만약 이렇게 할 수 없다면 저의 코드가 잘못된 것이겠지요. 이는 곧 리팩토링을 해야함을 시사합니다.
그래서 이번에는 public 메서드를 통해 간접적으로 private 접근제한자로 캡슐화된 메서드들을 테스트 해보았습니다. 아래에 유저에게 구입금액을 입력받고 이를 테스트 하는것을 예시로 보여드리겠습니다.

ValueValidator.java

public class ValueValidator { public boolean validatePurchasedPrice(String inputPurchasedPrice) { validatePurchasedPriceValue(inputPurchasedPrice); validatePurchasedPriceValueRange(inputPurchasedPrice); return true; } private void validatePurchasedPriceValue(String inputPurchasedPrice) { if (!inputPurchasedPrice.matches("\\d+")) { throw new IllegalArgumentException(ErrorMessage.ILLEGAL_NUMBER_FORMAT.getMessage()); } } private void validatePurchasedPriceValueRange(String inputPurchasedPrice) { int purchasedPrice = Integer.parseInt(inputPurchasedPrice); if (purchasedPrice < 1000) { throw new IllegalArgumentException(ErrorMessage.INSUFFICIENT_PURCHASE_AMOUNT.getMessage()); } } ...
Java
복사

validatePurchasedPrice

여기에 validatePurchasedPriceValuevalidatePurchasedPriceValueRange 메서드를 불러옵니다.
이 두 메서드에서 예외가 발생하지 않으면 정상적인 값이라는 소리이므로 true 를 반환합니다.

ValueValidatorTest.java

class ValueValidatorTest { private ValueValidator valueValidator; @BeforeEach public void setup() { valueValidator = new ValueValidator(); } @DisplayName("구매 가격에 숫자 외 다른 문자가 있으면 예외를 발생합니다.") @Test void 구매_가격_테스트_1() { assertThatThrownBy(() -> valueValidator.validatePurchasedPrice("1000j")) .isInstanceOf(IllegalArgumentException.class); } @DisplayName("구매 가격이 1000 원 이하일 때 예외를 발생합니다.") @Test void 구매_가격_테스트_2() { assertThatThrownBy(() -> valueValidator.validatePurchasedPrice("900")) .isInstanceOf(IllegalArgumentException.class); }
Java
복사
이제 위에 있는 ValueValidator 클래스의 public 메서드인 validatePurchasedPrice 를 이용하여 예외 처리가 제대로 진행되는지 테스트를 할 겁니다. 이렇게 간접적으로 테스트를 할 수 있습니다. 이전에는 private 메서드를 억지로 끌고와서 테스트를 한 셈이죠.  지금에라도 방법을 알아서 다행입니다.