개발 가이드 공통 (Common) 테스트 작성 규칙
최종 수정:

테스트 작성 규칙

JUnit5 + Mockito 기반 단위/통합 테스트 작성 규칙

테스트 작성 규칙

기본 원칙

  • 테스트 없는 코드는 완성된 코드가 아니다
  • 단위 테스트(Unit Test)와 통합 테스트(Integration Test) 구분
  • 테스트 코드도 프로덕션 코드와 동일한 품질 기준 적용
  • Given-When-Then 패턴 사용
  • Coverage 목표: 핵심 비즈니스 로직 80% 이상

테스트 클래스 구조

src/test/java/com/scraping/agent/
├── domain/
│   ├── news/
│   │   ├── service/
│   │   │   └── NewsServiceTest.java        # 단위 테스트 (Mockito)
│   │   └── repository/
│   │       └── NewsItemRepositoryTest.java # DB 통합 테스트
│   └── notes/
│       └── service/
│           └── PostServiceTest.java
└── global/
    └── health/
        └── HealthControllerTest.java       # Controller 슬라이스 테스트

단위 테스트 (Service)

@ExtendWith(MockitoExtension.class)
class PostServiceTest {

    @Mock
    private PostRepository postRepository;

    @InjectMocks
    private PostService postService;

    @Test
    @DisplayName("포스트 등록 성공")
    void regPost_success() {
        // Given
        PostCreateReq req = new PostCreateReq("제목", "내용", "BACKEND");
        Post savedPost = Post.builder()
                .title("제목").content("내용").category("BACKEND").build();
        given(postRepository.save(any())).willReturn(savedPost);

        // When
        PostDetailRes result = postService.regPost(req);

        // Then
        assertThat(result.getTitle()).isEqualTo("제목");
        verify(postRepository).save(any(Post.class));
    }

    @Test
    @DisplayName("존재하지 않는 포스트 조회 시 예외 발생")
    void getPost_notFound_throwsException() {
        // Given
        Long nonExistentId = 999L;
        given(postRepository.findById(nonExistentId)).willReturn(Optional.empty());

        // When & Then
        assertThatThrownBy(() -> postService.getPost(nonExistentId))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining("찾을 수 없습니다");
    }
}

Repository 통합 테스트

@DataJpaTest
@ActiveProfiles("test")
class PostRepositoryTest {

    @Autowired
    private PostRepository postRepository;

    @Autowired
    private TestEntityManager em;

    @Test
    @DisplayName("제목 중복 여부 확인")
    void existsByTitle_duplicate() {
        // Given
        Post post = Post.builder().title("중복 제목").content("내용").category("BACKEND").build();
        em.persist(post);
        em.flush();

        // When
        boolean exists = postRepository.existsByTitle("중복 제목");

        // Then
        assertThat(exists).isTrue();
    }
}

Controller 슬라이스 테스트

@WebMvcTest(NotesController.class)
class NotesControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private PostService postService;

    @Test
    @DisplayName("노트 목록 페이지 200 반환")
    void notesList_returnsOk() throws Exception {
        // Given
        given(postService.getPostPage(anyInt(), any())).willReturn(Page.empty());

        // When & Then
        mockMvc.perform(get("/notes")
                        .param("page", "0"))
                .andExpect(status().isOk())
                .andExpect(view().name("notes/list"));
    }

    @Test
    @DisplayName("REST API 노트 등록 201 반환")
    void regPost_returnsCreated() throws Exception {
        String body = """
                {"title": "테스트", "content": "내용", "category": "BACKEND"}
                """;
        PostDetailRes res = new PostDetailRes(1L, "테스트", "내용", "BACKEND", LocalDateTime.now());
        given(postService.regPost(any())).willReturn(res);

        mockMvc.perform(post("/api/v1/posts")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(body))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.title").value("테스트"));
    }
}

테스트 설정 파일

# src/test/resources/application.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb;MODE=MySQL
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true

AssertJ 사용 (권장)

// assertThat (AssertJ) — 가독성 높음
assertThat(result).isNotNull();
assertThat(result).hasSize(3);
assertThat(result.getTitle()).isEqualTo("Spring Boot 입문");
assertThat(result).extracting("title").contains("Spring Boot");
assertThatThrownBy(() -> service.method()).isInstanceOf(RuntimeException.class);

// assertEquals (JUnit 기본) — 간단한 경우에만
assertEquals(3, result.size());

테스트 픽스처 팩터리

// 테스트 전용 픽스처 생성 클래스
class PostFixture {

    static Post createPost(String title, String category) {
        return Post.builder()
                .title(title)
                .content("테스트 내용")
                .category(category)
                .build();
    }

    static PostCreateReq createReq(String title) {
        return new PostCreateReq(title, "테스트 내용", "BACKEND");
    }
}

테스트 작성 체크리스트

  • [ ] 테스트 메서드명 @DisplayName으로 의도 명시
  • [ ] Given-When-Then 구조 준수
  • [ ] 하나의 테스트 메서드는 하나의 동작만 검증
  • [ ] 픽스처 생성 메서드 분리 (코드 중복 방지)
  • [ ] verify()로 의도치 않은 호출 검증
  • [ ] 외부 의존성 @Mock 처리 (단위 테스트)
  • [ ] @DataJpaTest로 DB 레이어 분리 테스트
AI 문서 검색

현재 페이지 내용을 기반으로 질문하세요.