Closed Jonghai closed 1 year ago
테스트 범위는 테스트의 목적과 수행하는 사람에 따라 달라진다.
테스트 범위에 따른 테스트 종류 3가지
기능 테스트는 사용자 입장에서 시스템이 제공하는 기능이 올바르게 동작하는지 확인한다.
시스템을 구동하고 사용하는데 필요한 모든 구성요소가 필요
기능 테스트틑 사용자가 웹브라우저나 모바일 앱부터 시작해서 데이터베이스나 외부 서비스에 이르기까지 모든 구성 요소를 하나로 엮어서 진행
끝에서 끝까지 올바른지 검사하기 때문에 E2E(End to end)테스트로도 볼 수 있다.
기능 테스트가 사용자 입장에서 테스트하는 데 반해 통합 테스트는 소프트웨어의 코드를 직접 테스트한다.
단위 테스트는 개별 코드나 컴포넌트가 기대한대로 동작하는지 확인한다.
TDD를 하는 지 여부에 상관없이 테스트 코드를 작성하는 개발자는 단위 테스트와 통합테스트를 섞어서 작성한다.
위의 문제들은 단위 테스트의 대역으로 처리하여 테스트를 진행하면 테스트 실행 속도를 높이고 테스트 결과를 확인할 수 있다.
그래도 각 구성 요소가 올바륵게 연동되는 것을 확인하기 위해서는 통합 테스트는가 필요하다.
기능 테스트나 통합 테스트에서 모든 예외 상황을 테스트하면 단위 테스트는 줄어든다.
각 테스트가 다루는 내용이 중복되기 때문.
하지만 테스트 속도는 단위테스트가 가장 빠르기 때문에 가능하면 단위 테스트에 다양한 상황을 다루고, 통합 테스트나 기능 테스트는 주요 상황에 초점을 맞춰야 한다.
테스트 실행 속도가 느려지면 테스트 작성을 하지 않거나 테스트 실행을 생략하는 상황이 벌어진다.
외부 연동 대상은 쉽게 제어할 수 없기 떄문에 연동해야 할 대상이 늘어날수록 통합테스트도 힘들어진다.
모든 외부 연동 대상을 통합 테스트에서 다룰 수 없지만, 일부 외부 대상은 어느정도 제어가 가능하다.
통합 테스트는 실제로 DB를 사용한다.
예) 동일 ID가 존재하지 않을 때 회원정보를 올바르게 저장하는지 검증
@Test
void 존재하지_않으면_저장함(){
//상황 : DELETE쿼리 실행
jdbcTemplate.update("delete from user where id=?", "cbk");
//실행
register.register("cbk","strongpw","email@email.com");
//결과 확인 : SELECT 쿼리 실행
SqlRowSet rs = jdbcTemplate.queryForRowSet("select from user where id = ?, ","cbk");
rs.next();
assertEquals("email@email.com",rs.getString("email"));
}
단위 테스트는 대역을 사용하여 가짜 구현을 통해 동일 ID가 존재하는 상황을 만들어 테스트
통합테스트하기 어려운 대상이 외부 서버이다.
예) 외부 카드사 API를 이용해서 카드번호가 유효한지 확인
class CardNumberValidatorTest {
private WireMockServer wireMockServer;
@BeforeEach
void setUp(){
wireMockServer = new WireMockServer(options().port(8089));
wireMockServer.stsrt();
}
@AfterEach
void tearDown(){
wireMockServer.stop();
}
@Test
void valid(){
wireMockServer.stubFor(post(urlEqualTo("/card"))
.withRequestBody(equalTo("1234567890"))
.wilReturn(aResponse()
.withHeader("Content-Type", "text/pain")
.withBody("ok")));
CardNumberValidator validator = new CardNumberValidator("http://localhost:8089");
CardValidity validity = validator.validate("1234567890");
assertEquals(CardValidity.VALID,validity);
}
@Test
void timeout(){
wireMockServer.stubFor(post(urlEqualTo("/card"))
.wilReturn(aResponse()
.withFixedDelay(5000)));
CardNumberValidator validator = new CardNumberValidator()("http://localhost:8089");
CardValidity validity = validator.validate("1234567890");
assertEquals(CardValidity.TIMEOUT, validity);
}
}
CardNumberValidator 자체를 테스트하려면 정해진 규칙에 맞게 통신할 수 있는 서버가 필요하다. WireMock을 사용하면 서버 API를 스텁으로 대체할 수 있다.
WireMockServer는 HTTP 서버를 흉내 낸다.
WireMockServer 사용법
- 테스트 실행 전에 WireMockServer를 시작한다. 실제 HTTP 서버가 뜬다.
- 테스트에서 WireMockServer의 동작을 기술한다.
- HTTP 연동을 수행하는 테스트를 실행한다.
- 테스트 실행 후에 WireMockServer를 중지한다.
@BeforeEach 메서드로 WireMockServer를 생성.
@AfterEach 메서드로 WireMockServer를 중지.
CardValidty validty = validator.validate("123456789");
결과적으로 유효한 카드번호에 대한 테스트를 수행할 수 있게 된다.
스프링 부트를 사용한다면 내장 톰캣을 이용해서 API에 대한 테스트를 JUnit 코드로 작성할 수 있다.
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
스프링 부트는 테스트에서 웹 환경을 구동할 수 있는 기능을 제공한다.
이 기능을 사용해서 내장 서버를 구동하고 스프링 웹 어플리케이션을 실행한다.
HTTP를 이용해서 API를 호출한 결과를 검증.
실제 구동한 서버에 대해 HTTP로 연결해서 요청을 전송하고 응답을 받으므로 API에 대한 기능 테스트로 사용할 수 있다.
Chapter 9. 테스트 범위와 종류
목표