35DB1B8F-33F1-4CB3-BCE7-2C28C394C04E.jpeg

테스트 관련 용어는 문맥이나 사용자에 따라 의미가 다를 때도 있다. 예를 들어 개발 완료 후에 진행하는 최종 테스트를 ‘통합 테스트' 라고 부르기도 한다. 고객의 입장에서 요구한 기능을 올바르게 구현했는지 수행하는 테스트를 ‘인수 테스트' 라고 부르는데 요건을 완료했는지 정의하기 위해 작성한 테스트를 ‘인수 테스트' 라고 부르기도 한다.

기능 테스트와 E2E 테스트

기능 테스트(Functional Testing) 는 사용자 입장에서 시스템이 제공하는 기능이 올바르게 동작하는지 확인한다. 이 테스트를 수행하려면 시스템을 구동하고 사용하는데 필요한 모든 구성 요소가 필요하다. 기능 테스트는 사용자가 직접 사용하는 웹 브라우저나 모바일 앱부터 시작해서 데이터베이스나 외부 서비스에 이르기까지 모든 구성 요소를 하나로 엮어서 진행한다. 기능 테스트는 끝에서 끝까지 올바른지 검사하기 때문에 E2E(End to End) 테스트 로도 볼 수 있다.

QA 조직에서 수행하는 테스트가 주로 기능 테스트이다. 이때 테스트는 시스템이 필요로 하는 데이터를 입력하고 결과가 올바른지 확인한다.

통합 테스트

통합 테스트(Integration Testing) 는 시스템의 각 구성 요소가 올바르게 연동되는지 확인한다. 기능 테스트가 사용자 입장에서 테스트하는 데 반해 통합 테스트는 소프트웨어의 코드를 직접 테스트 한다.

단위 테스트

단위 테스트(Unit Testing) 는 개별 코드나 컴포넌트가 기대한대로 동작하는지 확인한다. 한 클래스나 한 메서드와 같은 작은 단위를 테스트한다.

기능 테스트 vs 통합 테스트

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

통합 테스트하기 어려운 대상 중 하나는 외부 서버이다. 스프링에서는 외부 서버에 대한 테스트를 위해 제공하는 기능이 있다. WireMock 을 이용하면 서버 API 를 스텁으로 대체할 수 있다.

CardNumberValidator.java

package chap09.autodebit;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.net.http.HttpTimeoutException;
import java.time.Duration;

public class CardNumberValidator {

    private String server;

    public CardNumberValidator(String server) {
        this.server = server;
    }

    public CardValidity validate(String cardNumber) {
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(server + "/card"))
                .header("Content-Type", "text/plain")
                .POST(BodyPublishers.ofString(cardNumber))
                .timeout(Duration.ofSeconds(3))
                .build();
        try {
            HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
            switch (response.body()) {
                case "ok": return CardValidity.VALID;
                case "bad": return CardValidity.INVALID;
                case "expired": return CardValidity.EXPIRED;
                case "theft": return CardValidity.THEFT;
                default: return CardValidity.UNKNOWN;
            }
        } catch (HttpTimeoutException e) {
            return CardValidity.TIMEOUT;
        } catch (IOException | InterruptedException e) {
            return CardValidity.ERROR;
        }
    }
}

https://github.com/madvirus/tddb/blob/master/chap09/src/main/java/chap09/autodebit/CardNumberValidator.java

CardNumberValidatorTest.java

package chap09.autodebit;

import com.github.tomakehurst.wiremock.WireMockServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class CardNumberValidatorTest {

    private WireMockServer wireMockServer;

    @BeforeEach
    void setUp() {
        wireMockServer = new WireMockServer(options().port(8089));
        wireMockServer.start(); // 8089번 포트로 mock 서버 구동
    }

    @AfterEach
    void tearDown() {
        wireMockServer.stop(); // 서버 종료
    }

    @Test
    void valid() {
        wireMockServer.stubFor(post(urlEqualTo("/card"))
                .withRequestBody(equalTo("1234567890"))
                .willReturn(aResponse()
                        .withHeader("Content-Type", "text/plain")
                        .withBody("ok"))
        ); // mock 서버에 원하는 동작 설정

        CardNumberValidator validator =
                new CardNumberValidator("<http://localhost:8089>");
        CardValidity validity = validator.validate("1234567890");
        assertEquals(CardValidity.VALID, validity);
    }

    @Test
    void timeout() {
        wireMockServer.stubFor(post(urlEqualTo("/card"))
                .willReturn(aResponse()
                        .withFixedDelay(5000))
        );

        CardNumberValidator validator =
                new CardNumberValidator("<http://localhost:8089>");
        CardValidity validity = validator.validate("1234567890");
        assertEquals(CardValidity.TIMEOUT, validity);
    }
}