
테스트 코드는 단순히 코드가 예상대로 동작하는지 확인하는 것을 넘어서, 코드의 의도와 동작을 명확하게 설명해야 합니다. 이를 위해 각 테스트는 하나의 목적만 가져야 하며, 여러 개의 목적을 포함하면 테스트가 복잡해지고 유지보수성이 낮아집니다.
아래 코드는 반복문과 조건문을 사용하여 여러 테스트 케이스를 한 테스트 메서드에서 처리하고 있습니다. 하지만 이렇게 작성하면 테스트가 복잡해지고, 특정 케이스에서 실패할 경우 원인을 빠르게 파악하기 어렵습니다.
@DisplayName("사용자 권한이 특정 권한에 해당하는지 확인한다.")
@Test
void checkUserRole() {
// given
UserRole[] roles = UserRole.values();
for (UserRole role : roles) {
if (role == UserRole.ADMIN) {
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isTrue();
} else {
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isFalse();
}
}
}
이 코드는 여러 역할(Role)을 한 번에 검증하려다 보니 테스트 목적이 불분명해졌습니다. 실패 시 어떤 조건에서 문제가 발생했는지 빠르게 알아내기 어렵습니다.
각 조건을 개별 테스트로 분리하면 가독성과 유지보수성이 향상됩니다.
@DisplayName("ADMIN 권한인지 확인한다.")
@Test
void isAdminRole() {
// given
UserRole role = UserRole.ADMIN;
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isTrue();
}
@DisplayName("USER 권한이 ADMIN인지 확인한다.")
@Test
void isNotAdminForUserRole() {
// given
UserRole role = UserRole.USER;
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isFalse();
}
@DisplayName("GUEST 권한이 ADMIN인지 확인한다.")
@Test
void isNotAdminForGuestRole() {
// given
UserRole role = UserRole.GUEST;
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isFalse();
}
이처럼 각 테스트를 분리하면 실패 시 원인을 빠르게 찾을 수 있고, 테스트 목적이 더 명확해집니다.
@ParameterizedTest를 활용한 개선테스트 내에서 if 문이나 반복문을 사용하지 않는 것이 바람직합니다. 동일한 테스트를 여러 번 실행해야 한다면 @ParameterizedTest를 사용하는 것이 더 좋은 방법입니다.
private static Stream<Arguments> provideRolesForAdminCheck() {
return Stream.of(
Arguments.of(UserRole.ADMIN, true),
Arguments.of(UserRole.USER, false),
Arguments.of(UserRole.GUEST, false)
);
}
@MethodSource("provideRolesForAdminCheck")
@ParameterizedTest
@DisplayName("권한이 ADMIN인지 확인한다.")
void checkAdminRoleParameterized(UserRole role, boolean expected) {
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isEqualTo(expected);
}
@ParameterizedTest를 사용하면 반복문 없이도 여러 테스트 케이스를 간결하게 표현할 수 있습니다.
@TestFactory를 활용한 동적 테스트여러 시나리오를 검증할 때 @TestFactory를 활용하면 동적으로 테스트를 생성하고 실행할 수 있습니다.
@DisplayName("권한 변경 시나리오")
@TestFactory
Collection<DynamicTest> roleChangeScenario() {
// given
User user = new User("John", UserRole.USER);
return List.of(
DynamicTest.dynamicTest("USER 권한을 ADMIN으로 변경할 수 있다.", () -> {
// when
user.changeRole(UserRole.ADMIN);
// then
assertThat(user.getRole()).isEqualTo(UserRole.ADMIN);
}),
DynamicTest.dynamicTest("ADMIN 권한을 GUEST로 변경할 수 있다.", () -> {
// when
user.changeRole(UserRole.GUEST);
// then
assertThat(user.getRole()).isEqualTo(UserRole.GUEST);
}),
DynamicTest.dynamicTest("GUEST 권한을 USER로 변경할 수 있다.", () -> {
// when
user.changeRole(UserRole.USER);
// then
assertThat(user.getRole()).isEqualTo(UserRole.USER);
})
);
}
@TestFactory는 여러 개의 테스트를 한 번에 정의하고 동적으로 실행할 때 유용합니다.
@ParameterizedTest와 @TestFactory를 활용하여 테스트 중복을 줄이고 가독성을 높인다.이러한 원칙을 따르면 테스트 코드가 단순해지고 유지보수성이 높아집니다.
📌 이 글은 TDD 강의를 학습한 내용을 바탕으로 재구성하였습니다. 문제가 되는 부분이 있다면 수정하겠습니다.