Java 테스트 코드 왜 궁금했을까❓
뉴스타 서비스에서는 테스트 코드를 작성하여 서비스의 코드 품질을 높이고 장애를 사전에 방지하고자 작성해보려고 한다. 또한, 코드 수정 시 빠르게 검증하여 작업의 생산성을 향상시키고자 한다.
1. 단위 테스트 / 통합 테스트
1.1. 단위 테스트 (Unit Test)
- 하나의 기능 또는 함수를 기준으로 독립적으로 수행하는 가장 작은 단위의 테스트
- 하나의 기능이 정상적으로 동작하는지를 테스트하는 것으로 "어떤 기능이 실행되면 어떤 결과가 나온다" 정도로 테스트한다.
1.2. 단위 테스트 장점
- 테스트에 대한 시간과 비용을 절감
- 새로운 기능을 추가하거나 수정 시 빠른 테스트 가능
- 리팩토링 시에 안정성 확보
- 코드에 대한 문서화
1.3. 통합 테스트 (Integration Test)
- 하나의 기능 또는 함수를 통합하는 과정에서 상호 간의 호환성을 확인하기 수행하는 테스트
- 어플리케이션의 여러 개의 모듈로 구성되어 있는데, 모듈 간의 상호 연계가 잘 되어 동작하는지 검증하는 것이다.
- 클라이언트로부터 API를 호출하여 올바르게 동작하는지 정도로 테스트한다.
1.4. 통합 테스트 장점
- 컴포넌트 간의 상호작용이 올바르게 동작하는지 확인 가능
- API 및 서비스 유효성을 검사 가능
2. 프로젝트 적용
테스트는 given-when-then 패턴으로 작성하여 가독성을 높일 수 있다. 테스트 코드는 문서의 역할도 하기 때문에 가독성은 중요하다고 할 수 있다.
- JUnit과 Mockito를 활용하여 통합 테스트를 진행하려고 한다.
@SpringBootTest
@AutoConfigureMockMvc(addFilters = true)
class MemberControllerTest {
...
@Test
void matchingMember() throws Exception {
// given
// when
mockMvc.perform(get("/members")
.header("X-User-Id", "feb91399-aaf3-40ef-856d-35e6ddf8befb")) // 사용자 헤더
// then
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.statusCode").value(200))
.andExpect(jsonPath("$.statusName").value("OK"))
.andExpect(jsonPath("$.message").value("OK"))
.andExpect(jsonPath("$.data.pw").value("feb91399-aaf3-40ef-856d-35e6ddf8befb"))
.andExpect(jsonPath("$.data.signDate").exists());
}
@Test
void createMember() throws Exception {
// given
// when
mockMvc.perform(post("/members")
.contentType(MediaType.APPLICATION_JSON) // Content-Type 헤더
.content("{\"categories\":[100, 200, 300]}"))
// then
.andExpect(status().isCreated())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.statusCode").value(201))
.andExpect(jsonPath("$.statusName").value("CREATED"))
.andExpect(jsonPath("$.message").value("Created Success"))
.andExpect(jsonPath("$.data.pw").exists())
.andExpect(jsonPath("$.data.signDate").exists());
}
}
- 유저 조회와 유저 생성에 대한 통합 테스트를 진행하고 있다.
- perform()을 통해 HTTP 요청을 실행하고 andExpect()를 통해 Response를 검증한다.
@SpringBootTest
@AutoConfigureMockMvc(addFilters = true)
class RecordControllerTest {
...
@Test
void getRecords() throws Exception {
//given
//when
mockMvc.perform(get("/records")
.header("X-User-Id", "feb91399-aaf3-40ef-856d-35e6ddf8befb"))
//then
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.statusCode").value(200))
.andExpect(jsonPath("$.statusName").value("OK"))
.andExpect(jsonPath("$.message").value("OK"));
}
@Test
void getRecordLikes() throws Exception {
//given
//when
mockMvc.perform(get("/records/likes")
.header("X-User-Id", "feb91399-aaf3-40ef-856d-35e6ddf8befb"))
//then
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.statusCode").value(200))
.andExpect(jsonPath("$.statusName").value("OK"))
.andExpect(jsonPath("$.message").value("OK"));
}
@Test
void createRecord() throws Exception {
//given
//when
mockMvc.perform(post("/records")
.header("X-User-Id", "feb91399-aaf3-40ef-856d-35e6ddf8befb")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"articleId\":1}"))
//then
.andExpect(status().isCreated())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.statusCode").value(201))
.andExpect(jsonPath("$.statusName").value("CREATED"))
.andExpect(jsonPath("$.message").value("Created Success"));
}
@Test
void updateRecordLikes() throws Exception {
//given
//when
mockMvc.perform(patch("/records")
.header("X-User-Id", "feb91399-aaf3-40ef-856d-35e6ddf8befb")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"articleId\":1 , \"likes\":true }"))
//then
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.statusCode").value(200))
.andExpect(jsonPath("$.statusName").value("OK"))
.andExpect(jsonPath("$.message").value("OK"));
}
}
- 뉴스 시청 기록 API에 대해 통합 테스트 코드를 작성해보았다.
- 이 외에도 위와 같은 형식으로 API 테스트를 진행했다.