[4기 이현호] Week2-1 : 바우처 서비스에 대한 테스트 코드 구현#794
Conversation
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| class VoucherServiceTest { |
There was a problem hiding this comment.
이 부분에서 Mock을 쓰는게 더 좋을지 고민이 되었는데,
생성자로 의존성을 간단하게 주입할 수 있는 것 같아서 그냥 쓰지 않았습니다.
뭔가 Mock을 꼭 써야만 하는 상황이 없을 수도 있겠다는 생각도 드는데(생성자로 주입받는 클래스라면)
꼭 Mocking을 써야만 해결되는 테스트가 있나요??
There was a problem hiding this comment.
우선 '단위테스트'의 개념부터 잘 이해하셔야 할것 같습니다.
현재는 service 레이어의 단위테스트이기 때문에 해당 클래스가 가지는 의존성을 제거하고 서비스 로직 자체에만 집중해야합니다.
따라서 의존성들을 모두 mocking시켜주는 것이죠.
또 현재는 메모리 repository라서 매우 가볍기 때문에 이렇게 해도 큰 문제는 없다고 생각할 수 있습니다.
(다만 빡빡한 기준으로 보면 service의 단위테스트라고 할 수는 없습니다)
하지만 jdbc repository라고 생각한다면.. repository에서는 spring의 도움을 받아 datasource bean까지 주입받아야 합니다.
service 레이어 단위테스트에서 이런 의존성까지 신경쓰고 싶지 않기 때문에 mocking을 한다고 생각하시면 될것 같네요~
지금 테스트는 그냥 두고, jdbc 적용 후에 mocking으로 테스트해주세요~!
There was a problem hiding this comment.
There was a problem hiding this comment.
- (2-3) PR에 mocking 적용한 단위 테스트 작성해보았습니다~! mockito는 처음 써봤는데 정말 좋은 공부가 되었습니다.
- 단위 테스트의 격리를 바라보는 관점이 두 가지 있다는 걸 처음 알게 되었습니다. 개인적으로 동작 단위로 격리하는 고전파 의견이 좀 더 실용적이라 느껴졌습니다. 의존하고 있는 객체의 문제로 클래스가 실패하더라도, 그것을 발견하는데 의의가 있다는 의견에서 특히 그랬구요. 팀원들과 두 방식의 장단점에 대해서 이야기 나눠봐야겠습니다~~
|
|
||
| private static void validatePositiveNumber(String input) { | ||
| if (!IS_NUMERIC_REGEX.matcher(input).matches()) { | ||
| throw new IllegalArgumentException(ERROR_MESSAGE_NOT_POSITIVE_INTEGER_VALUE); |
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| class VoucherServiceTest { |
There was a problem hiding this comment.
우선 '단위테스트'의 개념부터 잘 이해하셔야 할것 같습니다.
현재는 service 레이어의 단위테스트이기 때문에 해당 클래스가 가지는 의존성을 제거하고 서비스 로직 자체에만 집중해야합니다.
따라서 의존성들을 모두 mocking시켜주는 것이죠.
또 현재는 메모리 repository라서 매우 가볍기 때문에 이렇게 해도 큰 문제는 없다고 생각할 수 있습니다.
(다만 빡빡한 기준으로 보면 service의 단위테스트라고 할 수는 없습니다)
하지만 jdbc repository라고 생각한다면.. repository에서는 spring의 도움을 받아 datasource bean까지 주입받아야 합니다.
service 레이어 단위테스트에서 이런 의존성까지 신경쓰고 싶지 않기 때문에 mocking을 한다고 생각하시면 될것 같네요~
지금 테스트는 그냥 두고, jdbc 적용 후에 mocking으로 테스트해주세요~!
| @DisplayName("정액 바우처 읽어오기 테스트") | ||
| @ParameterizedTest | ||
| @ValueSource(ints = {100, 500, 3340}) | ||
| void 바우처_읽기_테스트1(int amount) { |
There was a problem hiding this comment.
테스트 메소드명을 한글로 작성하는건 @DisplayName까지 쓰고싶지 않은 이유가 큽니다.
둘 중 하나만 선택해서 하시는걸 추천드립니다. (웬만하면 지금은 정석대로 가시죠!)
또 테스트 설명을 작성할 때는 멘토님께서 말씀하신 것처럼 정확한 내용이 들어가야 합니다.
테스트 코드의 명칭은 3단계로 구성되었으면 좋겠어요.
어떤 상황에서 - 무엇을 하는데 - 어떤걸 기대한다.
여기서 "기대한다" 가 중요합니다.
참고 : #755 (review)
There was a problem hiding this comment.
그런 이유가 있었군요! 정석대로 바꾸고, 대부분의 테스트가 3단계로 구성되도록 변경하였습니다.
| class VoucherTypeTest { | ||
|
|
||
| @ParameterizedTest | ||
| @ValueSource(strings = {"accVoucher", "divideVoucher"}) | ||
| @DisplayName("바우처 타입에 없는 타입이 입력되면 예외를 발생시킨다.") | ||
| void voucherTypeTest(String input) { | ||
| assertThatThrownBy(() -> VoucherType.from(input)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("입력하신 " + input + "는 유효한 바우처 종류가 아닙니다.\n fix 또는 percent를 입력하세요.\n"); |
There was a problem hiding this comment.
Enum들 중 후에 값이 추가되서 테스트가 쉽게 깨질만한 파일들이 보여서 Enum에 대한 테스트는 제거 하였습니다.
하지만 다른 테스트들은 모두 정상 동작도 테스트하도록 변경하였습니다~!
| @ParameterizedTest | ||
| @ValueSource(strings = {"lists", "lists", "voucher"}) | ||
| @DisplayName("커멘드 명령어에 없는 명령어가 입력되면 예외를 발생시킨다.") | ||
| void programMenuTest(String input) { | ||
| assertThatThrownBy(() -> CommandType.from(input)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("입력하신 " + input + "는 유효한 커멘드가 아닙니다. \n exit, create, list 중 하나를 입력하세요.\n"); | ||
| } |
There was a problem hiding this comment.
given / when / then 주석은 웬만하면 항상 적용시켜주세요!~ (다른 테스트들도..)
when then이 겹친다 싶으면 // when + then 이런식으로라도 적어주세요.
There was a problem hiding this comment.
주석 적는것 마저도 귀찮으시다면 https://siyoon210.tistory.com/176 강력추천 합니다.
There was a problem hiding this comment.
대부분의 테스트가 3단계로 구성되도록 변경하였습니다!
꿀팁 감사합니다~!!
| voucherRepository.save(voucher4); | ||
|
|
||
| // then | ||
| Assertions.assertThat(voucherRepository.findAll()).isEqualTo(vouchers); |
There was a problem hiding this comment.
내부 객체 동등성까지 모두 비교해주나요? (동일성과 동등성 차이를 모른다면 검색해보세요~!)
There was a problem hiding this comment.
usingRecursiveFieldByFieldElementComparator() 메서드 사용해서 객체 순회하면서 내부 필드 동등성 비교하도록 변경했습니다!
|
|
||
| public VoucherDto readVoucher(UUID id) { | ||
| return voucherService.readAll(id); | ||
| public VoucherDto read(UUID id) { |
There was a problem hiding this comment.
메서드 네이밍에 대해 고민해보면 좋을것 같아요.
https://cocobi.tistory.com/27
There was a problem hiding this comment.
클래스명에 Voucher이 포함된 상황에 메서드명까지 중복될 필요가 없는 것 같습니다. Voucher은 제거하겠습니다!
| logger.warn("고객이 save되지 않았음. 잘 못된 입력 {}", customer); | ||
| throw new IllegalArgumentException(String.format("고객이 save되지 않았음. 잘 못된 입력 : %s", customer)); |
|
|
||
| class VoucherTest { | ||
|
|
||
| @DisplayName("<정액 할인 바우처> 할인 테스트") |
There was a problem hiding this comment.
"고정된 값을 입력하여 고정 할인을 진행할 수 있다." 라고 이름을 바꾸었습니다.
기대값을 명시해야 테스트의 구체적인 목적을 파악할 수 있겠다는 생각이 들었습니다.
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.ValueSource; | ||
|
|
||
| class VoucherServiceTest { |
There was a problem hiding this comment.
| @ParameterizedTest | ||
| @ValueSource(strings = {"lists", "lists", "voucher"}) | ||
| @DisplayName("커멘드 명령어에 없는 명령어가 입력되면 예외를 발생시킨다.") | ||
| void programMenuTest(String input) { | ||
| assertThatThrownBy(() -> CommandType.from(input)) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("입력하신 " + input + "는 유효한 커멘드가 아닙니다. \n exit, create, list 중 하나를 입력하세요.\n"); | ||
| } |
There was a problem hiding this comment.
주석 적는것 마저도 귀찮으시다면 https://siyoon210.tistory.com/176 강력추천 합니다.
📌 과제 설명
요구 사항
기본
바우처 관리 애플리케이션에 단위테스트를 작성해보세요.
바우처 관리 애플리케이션에서도 과정에서 다루었던 고객을 적용해보세요.
(1주차엔 파일로 관리하게 했다.) 바우처 정보를 DB로 관리해보세요.
✅ 구조
✅ 실행화면
✅ 궁금한 점
바우처의 로직이 틀린 부분, 메서드 네이밍이 잘 못된 부분 등이 있어서 수정했습니다. 따라서 테스트 코드가 아닌 일반 코드 영역에 수정된 부분이 있을 수 있으나, Files changed에서는 Test파일 위주로 봐주시면 될 것 같습니다!!