단위 테스트, 통합 테스트, 인수 테스트에 대해 설명합니다.


🤔 단위 테스트란?

  • 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());
    }
}




🙇🏻‍♂️ 참고사이트

댓글남기기