일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- Azure
- bind
- builder
- button
- c++
- c++ builder
- c03
- Today
- Total
기록
[테스트전략] 각 테스트는 하나의 목적만 가진다 본문
각 테스트는 하나의 목적만 가진다
테스트 코드는 단순히 코드가 잘 작동하는지 확인하는 데서 끝나는 것이 아니라, 코드의 의도와 동작 방식을 명확히 보여줄 수 있어야 합니다.
이를 위해 하나의 테스트는 하나의 목적만 가져야 하며, 여러 목적을 포함하면 테스트가 복잡해지고 의도를 이해하기 어려워집니다.
잘못된 테스트 코드 예제 : 반복문과 조건문을 포함한 예제
아래는 반복문과 조건문을 포함한 잘못된 테스트 코드 예제입니다. 이 코드는 여러 케이스를 하나의 테스트에 담아 작성되었기 때문에 가독성과 유지보수성이 떨어집니다.
@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();
}
if (role == UserRole.USER || role == UserRole.GUEST) {
// when
boolean result = UserRole.isAdmin(role);
// then
assertThat(result).isFalse();
}
}
}
이 코드는 반복문과 조건문을 사용해 여러 케이스를 처리하고 있습니다. 하지만 이렇게 작성하면 테스트의 목적이 명확하지 않고, 실패했을 때 어떤 케이스에서 문제가 발생했는지 파악하기 어렵습니다.
개선된 테스트 : 독립적인 테스트로 분리
각 조건을 독립적인 테스트로 분리하면 테스트 목적이 더 명확해지고 가독성과 유지보수성이 좋아집니다.
@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와 DynamicTest 활용
JUnit에서 제공하는 @ParameterizedTest와 DynamicTest를 활용하면 반복문 없이도 여러 테스트 케이스를 효율적으로 작성할 수 있습니다. 이를 통해 코드 중복을 줄이고 테스트를 더 깔끔하게 구성할 수 있습니다.
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);
}
DynamicTest : 시나리오 기반 테스트
테스트 간 강결합을 방지하기 위해 공유 변수를 사용하는 것은 지양해야 합니다. 여러 시나리오를 검증해야 할 경우 @DynamicTest를 활용해 순서대로 실행할 수 있습니다.
@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);
})
);
}
'교육 > 강의' 카테고리의 다른 글
[테스트전략] Test Fixture 독립성 보장 (0) | 2025.02.10 |
---|---|
[Mock] 스프링 환경에서 외부 시스템 테스트하기 (0) | 2025.02.06 |
[JpaTest] CQRS와 서비스 레이어에서의 트랜잭션 관리 (0) | 2025.02.05 |
[JpaTest] Spring Bean Validation 활용 가이드 (0) | 2025.02.04 |
[JpaTest] Spring Layered Architecture와 Layer 테스트 전략 (0) | 2025.02.03 |