Skip to content

테스트 표준

25.1. JUnit 5 테스트 작성 기준

25.1.1. 테스트 클래스 네이밍

대상네이밍 규칙예시
단위 테스트{대상클래스}TestUserServiceTest
통합 테스트{대상클래스}IntegrationTestUserControllerIntegrationTest
슬라이스 테스트{대상클래스}Test + 어노테이션으로 구분UserControllerTest (@WebMvcTest)

25.1.2. 테스트 메서드 네이밍

한글 서술형 메서드명을 허용합니다. 테스트 의도가 명확하게 드러나야 합니다.

java
@Test
void 이메일로_사용자를_조회한다() {
  // ...
}

@Test
void 존재하지_않는_사용자_조회_시_예외가_발생한다() {
  // ...
}

25.1.3. Given-When-Then 패턴

모든 테스트는 Given-When-Then 구조로 작성합니다.

java
@Test
void 이메일로_사용자를_조회한다() {
  // given
  String email = "user@tienipia.com";
  User expected = new User(1L, "홍길동", email);
  given(userRepository.findByEmail(email)).willReturn(Optional.of(expected));

  // when
  User result = userService.findByEmail(email);

  // then
  assertThat(result.getName()).isEqualTo("홍길동");
  assertThat(result.getEmail()).isEqualTo(email);
}

25.1.4. Assertion 라이브러리

  • AssertJ를 표준으로 사용합니다.
  • JUnit 5 기본 Assertions도 사용 가능하지만, AssertJ의 fluent API를 권장합니다.
java
// AssertJ (권장)
assertThat(result).isNotNull();
assertThat(result.getName()).isEqualTo("홍길동");
assertThat(users).hasSize(3).extracting("name").contains("홍길동");

// 예외 검증
assertThatThrownBy(() -> userService.findById(999L))
    .isInstanceOf(UserNotFoundException.class)
    .hasMessageContaining("999");

25.2. 테스트 분류

25.2.1. 단위 테스트

  • 단일 클래스 또는 메서드를 격리하여 테스트합니다.
  • 외부 의존성은 Mockito로 모킹합니다.
  • Spring 컨텍스트를 로드하지 않습니다.
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

  @Mock
  private UserRepository userRepository;

  @InjectMocks
  private UserServiceImpl userService;

  @Test
  void 사용자를_생성한다() {
    // given
    CreateUserRequest request = new CreateUserRequest("홍길동", "user@tienipia.com");
    given(userRepository.insert(request.name(), request.email()))
        .willReturn(UsersRecordFixture.create(1L, "홍길동", "user@tienipia.com"));

    // when
    UserResponse response = userService.createUser(request);

    // then
    assertThat(response.name()).isEqualTo("홍길동");
    then(userRepository).should().insert(request.name(), request.email());
  }
}

25.2.2. 슬라이스 테스트

특정 레이어만 로드하여 테스트합니다.

어노테이션대상 레이어용도
@WebMvcTestControllerAPI 요청/응답 검증
@JooqTestRepositoryjOOQ 쿼리 검증
@JsonTestJSON 직렬화DTO 변환 검증
java
@WebMvcTest(UserController.class)
class UserControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private UserService userService;

  @Test
  void 사용자_목록을_조회한다() throws Exception {
    given(userService.findAll())
        .willReturn(List.of(new UserResponse(1L, "홍길동", "user@tienipia.com")));

    mockMvc.perform(get("/api/users"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].name").value("홍길동"));
  }
}

25.2.3. Repository 테스트

jOOQ 기반 Repository 테스트는 @JooqTest 또는 @SpringBootTest + Testcontainers를 사용합니다.

@JooqTest 사용 예시:

java
@JooqTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class UserRepositoryTest {

  @Container
  static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
      .withDatabaseName("flowin_test")
      .withUsername("test")
      .withPassword("test");

  @DynamicPropertySource
  static void configureProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
  }

  @Autowired
  private DSLContext dsl;

  private UserRepository userRepository;

  @BeforeEach
  void setUp() {
    userRepository = new UserRepository(dsl);
  }

  @Test
  void 사용자를_생성하고_조회한다() {
    // given
    userRepository.insert("홍길동", "user@tienipia.com");

    // when
    Optional<UsersRecord> result = userRepository.findByEmail("user@tienipia.com");

    // then
    assertThat(result).isPresent();
    assertThat(result.get().getName()).isEqualTo("홍길동");
  }
}
  • Testcontainers가 실제 PostgreSQL 컨테이너를 실행하므로, PostgreSQL 고유 기능(인덱스, 타입 등)을 정확히 테스트할 수 있습니다.
  • Flyway 마이그레이션이 자동으로 적용되어 테스트 DB가 초기화됩니다.

25.2.4. 통합 테스트

  • @SpringBootTest로 전체 컨텍스트를 로드합니다.
  • 실제 데이터베이스 대신 Testcontainers PostgreSQL을 사용합니다. H2는 사용하지 않습니다.
  • 통합 테스트는 CI에서 반드시 실행합니다.

25.3. 커버리지 기준

25.3.1. 최소 커버리지

측정 기준최소 비율
라인 커버리지80% 이상
브랜치 커버리지70% 이상

25.3.2. 제외 대상

다음 항목은 커버리지 측정에서 제외합니다.

  • Configuration 클래스 (*Config.java)
  • DTO (단순 데이터 클래스)
  • Main 클래스 (*Application.java)
  • jOOQ 자동 생성 코드 (jooq/ 패키지)

JaCoCo에서 제외 설정:

xml
<configuration>
  <excludes>
    <exclude>**/*Config.*</exclude>
    <exclude>**/*Application.*</exclude>
    <exclude>**/dto/**</exclude>
    <exclude>**/jooq/**</exclude>
  </excludes>
</configuration>

25.3.3. 커버리지 리포트

  • JaCoCo가 HTML 리포트를 target/site/jacoco/에 생성합니다.
  • CI에서 리포트를 아티팩트로 저장하여 리뷰 시 참고합니다.

25.4. 테스트 데이터 관리

25.4.1. 테스트 픽스처

  • 테스트 데이터 생성은 팩토리 메서드 또는 빌더 패턴을 사용합니다.
  • 테스트 간 데이터 공유는 금지합니다. 각 테스트는 독립적으로 실행되어야 합니다.
java
class UserFixture {

  public static User createDefaultUser() {
    return new User(1L, "홍길동", "user@tienipia.com");
  }

  public static User createUser(String name, String email) {
    return new User(null, name, email);
  }
}

25.4.2. 테스트 원칙

  • 테스트는 독립적이어야 합니다. 실행 순서에 의존하지 않습니다.
  • 테스트는 반복 가능해야 합니다. 외부 상태에 의존하지 않습니다.
  • 테스트는 빠르게 실행되어야 합니다. 단위 테스트 전체가 30초 이내에 완료되는 것을 목표로 합니다.

TIENIPIA QUALIFIED STANDARD