JUnit Test 기본 기능들
@BeforeEach
@AfterEach
@BeforeAll
@AfterAll
@Nested
@DisplayName("주제 별로 테스트를 그룹지어서 파악하기 좋습니다.")
class Test1 {
@Test
@DisplayName("Test1 - test1()")
void test1() {
System.out.println("Test1.test1");
}
@Test
@DisplayName("Test1 - test2()")
void test2() {
System.out.println("Test1.test2");
}
}
@Nested
@DisplayName("Test2 다른 주제")
class Test2 {
@Test
@DisplayName("Test2 - test1()")
void test1() {
System.out.println("Test2.test1");
}
@Test
@DisplayName("Test2 - test2()")
void test2() {
System.out.println("Test2.test2");
}
}
@Nested
@DisplayName("주제 별로 테스트를 그룹지어서 파악하기 좋습니다.")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class Test1 {
@Order(1)
@Test
@DisplayName("Test1 클래스")
void test() {
System.out.println("\nTest1 클래스");
}
@Order(3)
@Test
@DisplayName("Test1 - test1()")
void test1() {
System.out.println("Test1.test1");
}
@Order(2)
@Test
@DisplayName("Test1 - test2()")
void test2() {
System.out.println("Test1.test2");
}
}
@RepeatedTest(value = 5, name = "반복 테스트 {currentRepetition} / {totalRepetitions}")
void repeatTest(RepetitionInfo info) {
System.out.println("테스트 반복 : " + info.getCurrentRepetition() + " / " + info.getTotalRepetitions());
}
@DisplayName("파라미터 값 활용하여 테스트 하기")
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9})
void parameterTest(int num) {
System.out.println("5 * num = " + 5 * num);
}
//assertEquals
@Test
@DisplayName("assertEquals")
void test1() {
Double result = calculator.operate(5, "/", 2);
assertEquals(2.5, result);
}
@Test
@DisplayName("assertEquals - Supplier")
void test1_1() {
Double result = calculator.operate(5, "/", 0);
// 테스트 실패 시 메시지 출력 (new Supplier<String>())
assertEquals(2.5, result, () -> "연산자 혹은 분모가 0이 아닌지 확인해보세요!");
}
@Test
@DisplayName("assertNotEquals")
void test1_2() {
Double result = calculator.operate(5, "/", 0);
assertNotEquals(2.5, result);
}
//assertTrue
@Test
@DisplayName("assertTrue 와 assertFalse")
void test2() {
assertTrue(calculator.validateNum(9));
assertFalse(calculator.validateNum(0));
}
//assertNotNull
@Test
@DisplayName("assertNotNull 과 assertNull")
void test3() {
Double result1 = calculator.operate(5, "/", 2);
assertNotNull(result1);
Double result2 = calculator.operate(5, "/", 0);
assertNull(result2);
}
//assertThrows
@Test
@DisplayName("assertThrows")
void test4() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> calculator.operate(5, "?", 2));
assertEquals("잘못된 연산자입니다.", exception.getMessage());
}
테스트 하면서 느낀점
UserService의 회원가입 기능을 테스트 하려고 했는데 mockito를 이용해 userRepository를 서비스에 주입하였고, service코드 마지막단에는 return userRepository.save()와 같은 형식으로 user를 반환했다.
하지만, mockito로 생성한 객체는 가짜객체이기때문에 어떤 메소드이던 null을 리턴한다. 처음에는 이것을 해결하려고 어떻게 하면 null이 아닌 다른 값을 반환해줄지 고민했는데 이런 고민들은 테스트라는 성격에 부합하지 않는 고민이었다.
그래서 userService.register()의 역할에 대해서 고민해보았다. 그 결과 userService.register()의 역할은 받아온 회원가입DTO를 이용하여 데이터의 유효성과 중복을 검증하고 User객체를 생성하는 역할을 하는 것이라는 결론을 내렸다.
따라서, 기존 return userRepository.save()를 userRepository.save(); return user로 코드를 변경하였고, userService.register()의 역할에만 집중한 테스트 코드를 작성할 수 있었다. 과제를 시작하기 전에 멘토님들께서 누누히 말씀하셨던 역할의 분리 개념이 조금 와닿을 수 있었다.
//기존 코드
return userRepository.save(
User.builder()
.username(request.getUsername())
.email(email)
.password(passwordEncoder.encode(password))
.userRole(UserRole.USER)
.build()
);
//수정한 코드
User user = User.builder()
.username(request.getUsername())
.email(email)
.password(passwordEncoder.encode(password))
.userRole(UserRole.USER)
.build();
userRepository.save(user);
return user;
테스트케이스를 작성하면서 Entity/Dto, Controller, Service, Repository 순으로 코드를 작성하라는 요구사항을 받았다. 그리고 테스트코드 작성시에 지켜야하는 FIRST규칙에 대한 내용도 공유받을 수 있었다. 여기서 의문이 든게 FIRST의 Timely 즉, 좋은 단위 테스트는 미루지 않고 즉시 작성한다. 라는 부분에서 내가 생각했을때의 백엔드 개발은 Repository -> Service -> Controller 순으로 이루어지는데 그럼 Repository의 테스트 케이스부터 작성해야하는것이 아닌가? 라는 생각이 들었다.
담당멘토님께 찾아가 여쭤보니 테스트코드 작성을 시작하는 레이어는 Controller나 Repository나 회사마다 다르게 설정한다고 한다. 실제로 멘토님도 Repository를 시작으로 테스트코드를 작성하고 있다고 하시고 나 또한 같은 생각이어서 우선 현재 나는 개발 순서대로Repository부터 테스트코드를 작성하는것이 옳다고 생각하기 때문에 Timely를 준수하면서 테스트코드를 작성해야겠다고 결정했다.
또한, Controller와 Service 테스트 코드를 작성하면서 Controller 테스트코드의 필요성을 크게 느끼지 못했었는데, 실제로 Controller 테스트코드는 스킵하는 경우도 많다고 했다. 왜나하면 Controller는 가장 앞단에서 데이터를 받고 뿌려주는 역할을 하기 때문에 테스트가 필요한 부분이 별로 없고 있다고 해도 테스트의 주제가 Service 레이어와 유사하기 때문이다.
마지막으로, 과제 해설 영상에서는 테스트코드가 작성된 기능도 있고 작성되지 않은 기능도 있었는데, 이 부분도 멘토님께 물어보니 본래 테스트코드는 구현한 모든 기능에 대해 작성하는 것이 올바르다고 하셨다. 어떤 테스트코드를 작성하던 회사에 가면 회사에 맞춰 테스트케이스를 진행하게 될텐데 우선은 모든 기능에 대한 단위 테스트와 통합테스트를 작성하면서 TDD가 무엇인지 연구하고 역량을 향상시키는데에 집중해야겠다
[ 참고자료 ]