단위 테스트, 통합 테스트, 인수 테스트
단위 테스트, 통합 테스트, 인수 테스트에 대해 설명합니다.
🤔 단위 테스트란?
- Unit Test라고 한다.
- 개발자 관점에서 테스트한다.
- 구현한 부분, 단위를 검증한다.
- 단위 기능을 중심으로 다른 외부 영향을 배제하고 테스트 하는 것이 목적이다.
- 협력 객체가 존재하는 경우에는 협력 객체가 원하는 결과를 응답하도록 모킹한다.
단위 테스트 범위
- 단위의 크기를 정확히 제시하기는 어렵다.
- 일반적으로 특정한 책임을 수행하면서, 고립될 수 있는 최소 단위
단위 테스트 예
class UnitTest {
@DisplayName("이름을 받아 자동차 생성")
@Test
public void createCar() {
// given
String name = "Boong-Boong";
// when
Car car = new Car(name);
// then
assertThat(car).isNotNull();
}
@DisplayName("숫자 4를 받으면 자동차가 1 전진한다.")
@Test
public void moveCar() {
// given
Car car = new Car("Boong-Boong");
// when
car.move(4);
// then
assertThat(car.getPosition()).isEqualTo(1);
}
}
협력 객체의 단위 테스트 예
@ExtendWith(MockitoExtension.class)
class ServiceTest {
@Mock
private CarRepository carRepository;
@InjectMocks
private CarService carService;
@DisplayName("이름을 받아 자동차를 저장한다.")
@Test
public void save(){
// given
final String NAME = "Boong-Boong";
when(carRepository.save(any())).thenReturn(Optional.of(new Car(NAME)));
// when
CarResponse carResponse = carService.save(NAME);
// then
assertThat(carResponse.getName()).isEqualTo(NAME);
}
}
@WebMvcTest(CarController.class)
public class CarControllerTest {
private static final String API_BASE_PATH = "/api/cars";
@Autowired
private MockMvc mockMvc;
@MockBean
private CarService carService;
@Autowired
private ObjectMapper objectMapper;
@DisplayName("자동차를 생성한다.")
@Test
public void createCar() throws Exception {
//given
CarRequest carRequest = new carRequest("Boong-Boong");
CarResponse carResponse = new CarResponse(1L, carRequest.getName());
given(carService.save(any())).willReturn(carResponse);
//when, then
mockMvc.perform(post(API_BASE_PATH)
.content(objectMapper.writeValueAsString(carRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(carResponse.getId()));
}
}
🤔 통합 테스트란?
- Integration Test라고 한다.
- 개발자 관점에서 테스트한다.
- 여러 기능을 조합하여 전체 비지니스 로직이 제대로 동작하는지 확인하는 것을 의미한다.
- 단위 기능 간의 의존성을 포함하여 테스트한다.
- 단위 테스트와 달리 개발자가 변경할 수 없는 부분(예: 외부 라이브러리)까지 묶어 검증할 때 사용한다.
통합 테스트 예
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class CarServiceTest {
@Autowired
private CarService carService;
@DisplayName("이름을 받아 자동차를 저장한다.")
@Test
public void save(){
// given
final String NAME = "Boong-Boong";
// when
CarResponse car = carService.save(NAME);
// then
assertThat(car.getName()).isEqualTo(NAME);
}
}
🤔 인수 테스트란?
- Acceptance Test라고 한다.
- 사용자 관점에서 테스트한다.
- 사용자 스토리(시나리오)에 맞춰 수행하는 테스트이다.
- 요구사항을 만족하는지를 검증한다.
- 다른 의사소통집단으로부터 시나리오를 받아(인수) 개발한다는 의미를 가지고 있다.
인수 테스트 예: MockMvc
- 실제로 서버를 구동하진 않고 모킹한 Spring MVC 환경을 구현
- MVC 서버와 요청을 날리는 곳의 스레드가 분리되므로
@Transaction
을 선언하여도Rollback
되지 않음.- 데이터를 직접 삭제해야함
@SpringBootTest
@AutoConfigureMockMvc
public class CarControllerMockTest {
private static final String API_BASE_PATH = "/api/cars";
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@DisplayName("자동차를 생성한다.")
@Test
public void createCar() throws Exception {
//given
CarRequest carRequest = new carRequest("Boong-Boong");
CarResponse carResponse = new CarResponse(1L, carRequest.getName());
//when, then
mockMvc.perform(post(API_BASE_PATH)
.content(objectMapper.writeValueAsString(carRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(carResponse.getId()));
}
}
인수 테스트 예: RestAssured
- 실제 서버를 구동하여 실제와 동일한 환경에서 테스트를 지원
- MockMvc와 마찬가지로 MVC 서버와 요청을 날리는 곳의 스레드가 분리되므로
@Transaction
을 선언하여도Rollback
되지 않음.- 데이터를 직접 삭제해야함
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AcceptanceTest {
@LocalServerPort
int port;
@Autowired
private DatabaseCleanup databaseCleanup; // 애플리케이션에 등록된 엔티티를 테이블을 비워주는 클래스를 만들어 (임의로 만든 클레스)
@BeforeEach // 매 테스트 실행 전에
public void setUp() {
RestAssured.port = port;
databaseCleanup.execute(); // 테이블을 비워준다.
}
}
public class CarAcceptanceTest extends AcceptanceTest{
private static final String API_BASE_PATH = "/api/cars";
@DisplayName("자동차를 생성한다.")
@Test
public void createCar(){
//give
CarRequest 자동차 = new carRequest("Boong-Boong");
//when
ExtractableResponse<Response> 자동차_생성_결과 = 자동차_생성_요청(자동차);
//then
자동차_생성_완료됨(자동차_생성_결과);
}
public ExtractableResponse<Response> 자동차_생성_요청(CarRequest request) {
return RestAssured
.given().log().all()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(request)
.when().post(API_BASE_PATH)
.then().log().all()
.extract();
}
public static void 자동차_생성_완료됨(ExtractableResponse<Response> response) {
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
}
}
댓글남기기