2jigoo / BookStudy-StartTdd

'테스트 주도 개발 시작하기' 스터디
2 stars 0 forks source link

[6주차] 테스트 범위와 종류 - jongha #26

Closed Jonghai closed 1 year ago

Jonghai commented 1 year ago

Chapter 9. 테스트 범위와 종류

스터디 일시 2023.09.11

목표

Jonghai commented 1 year ago

Chap.09 테스트 범위와 종류

테스트 범위

테스트 범위는 테스트의 목적과 수행하는 사람에 따라 달라진다.

테스트 범위에 따른 테스트 종류 3가지

기능 테스트와 E2E 테스트

기능 테스트는 사용자 입장에서 시스템이 제공하는 기능이 올바르게 동작하는지 확인한다.

통합 테스트

기능 테스트가 사용자 입장에서 테스트하는 데 반해 통합 테스트는 소프트웨어의 코드를 직접 테스트한다.

단위 테스트

단위 테스트는 개별 코드나 컴포넌트가 기대한대로 동작하는지 확인한다.

테스트 범위 간 차이

TDD를 하는 지 여부에 상관없이 테스트 코드를 작성하는 개발자는 단위 테스트와 통합테스트를 섞어서 작성한다.

위의 문제들은 단위 테스트의 대역으로 처리하여 테스트를 진행하면 테스트 실행 속도를 높이고 테스트 결과를 확인할 수 있다.

그래도 각 구성 요소가 올바륵게 연동되는 것을 확인하기 위해서는 통합 테스트는가 필요하다.

테스트 범위에 따른 테스트 코드 개수와 시간

기능 테스트나 통합 테스트에서 모든 예외 상황을 테스트하면 단위 테스트는 줄어든다.
각 테스트가 다루는 내용이 중복되기 때문.

하지만 테스트 속도는 단위테스트가 가장 빠르기 때문에 가능하면 단위 테스트에 다양한 상황을 다루고, 통합 테스트나 기능 테스트는 주요 상황에 초점을 맞춰야 한다.

테스트 실행 속도가 느려지면 테스트 작성을 하지 않거나 테스트 실행을 생략하는 상황이 벌어진다.

Jonghai commented 1 year ago

외부 연동이 필요한 테스트 예

외부 연동 대상은 쉽게 제어할 수 없기 떄문에 연동해야 할 대상이 늘어날수록 통합테스트도 힘들어진다.

모든 외부 연동 대상을 통합 테스트에서 다룰 수 없지만, 일부 외부 대상은 어느정도 제어가 가능하다.

스프링 부트와 DB 통합 테스트

통합 테스트는 실제로 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가 존재하는 상황을 만들어 테스트

WireMock을 이용한 REST 클라이언트 테스트

통합테스트하기 어려운 대상이 외부 서버이다.

예) 외부 카드사 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 기능 테스트

스프링 부트를 사용한다면 내장 톰캣을 이용해서 API에 대한 테스트를 JUnit 코드로 작성할 수 있다.

@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)

스프링 부트는 테스트에서 웹 환경을 구동할 수 있는 기능을 제공한다.
이 기능을 사용해서 내장 서버를 구동하고 스프링 웹 어플리케이션을 실행한다.

HTTP를 이용해서 API를 호출한 결과를 검증.
실제 구동한 서버에 대해 HTTP로 연결해서 요청을 전송하고 응답을 받으므로 API에 대한 기능 테스트로 사용할 수 있다.