단위 테스트 스타일

theonde·2022년 11월 4일
0

단위 테스트 스타일

  1. 출력 기반 테스트: 가장 품질이 좋다.
  • 아무데서나 사용할 수 없다.

  • 순수 함수 방식으로 작성된 코드에만 적용된다.

  1. 상태 기반 테스트: 두번째로 좋다.

  2. 통신 기반 테스트: 품질이 좋지 않다. 간헐적으로만 사용해야한다.

하나의 테스트에서 세 가지 스타일 모두를 함께 사용할 수 있다.

출력 기반 테스트 정의

  • 테스트 대상 시스템(SUT)에 입력을 넣고 생성되는 출력을 점검하는 방식이다.

    • 전역 상태나 내부 상태를 변경하지 않는 코드에만 적용되므로 반환 값만 검증하면 된다.

    • 부작용이 없고 SUT 작업 결과는 호출자에게 반환되는 값뿐이다.

public class PriceEngine {

    public BigDecimal calculateDiscount(Product ...products) {
        BigDecimal discount = BigDecimal.valueOf(products.length * 0.01);
        return discount.min(BigDecimal.valueOf(0.2));
    }
}

public class PriceEngineTest {

    @Test
    void discount_of_two_products() {
        Product product1 = new Product("Hand wash");
        Product product2 = new Product("Shampoo");
        PriceEngine sut = new PriceEngine();

        BigDecimal discount = sut.calculateDiscount(product1, product2);

        assertThat(discount).isEqualTo(BigDecimal.valueOf(0.02));
    }
}
  • 상품 수에 1%를 곱하고 그 결과를 20%로 제한한다.

  • 내부 컬렉션에 상품을 추가하거나 데이터베이스에 저장하지 않는다.

  • calculateDiscount메소드의 결과는 반환된 할인, 즉 출력 값 뿐이다.

    calculateDiscount의 메소드는 아무런 상태 변화 없이 product의 길이를 계산하여 나온 값을 출력만 한다.

  • 출력 기반 단위 테스트 스타일은 함수형이라고도 한다.

  • 부작용 없는 코드 선호를 강조하는 프로그래밍 방식인 함수형 프로그래밍에 뿌리를 두고 있다.

상태 기반 테스트 정의

  • 작업이 완료된 후 시스템 상태를 확인하는 것

  • "상태"라는 것은 SUT의 협력자 중 하나, 또는 데이터베이스 같은 프로세스 외부 의존성의 상태 등을 의미한다.

@Getter
public class Order {

    private List<Product> products = new ArrayList<>();

    public void addProduct(Product product) {
        products.add(product);
    }
}

public class OrderTest {

    @Test
    void adding_a_product_to_an_order() {
        Product product = new Product("Hand wash");
        Order sut = new Order();

        sut.addProduct(product);

        assertThat(sut.getProducts().size()).isEqualTo(1);
        assertThat(sut.getProducts().get(0)).isEqualTo(product);
    }
}
  • 위 테스트는 상품을 추가한 후 products 컬렉션을 검증한다.

  • 출력 기반 테스트와는 달리, addProduct메소드의 결과는 주문 "상태"의 변경이다.

    Order클래스 필드에 정의한 products의 상태에 변화가 일어났다.

통신 기반 테스트 정의

  • 목을 사용해 테스트 대상 시스템과 협력자 간의 통신을 검증한다.

  • 협력자를 목으로 대체하고 SUT가 협력자를 올바르게 호출하는지 검증한다.

@AllArgsConstructor
public class Controller {

    private EmailGateway emailGateway;

    public void greetUser(String email) {
        emailGateway.sendGreetingEmail(email);
    }
}

public class EmailGateway {

    public void sendGreetingEmail(String email) {
        // TODO: 2022/11/05  
    }
}

public class ControllerTest {

    @Test
    void sending_a_greeting_email() {
        EmailGateway emailGatewayMock = mock(EmailGateway.class);
        Controller sut = new Controller(emailGatewayMock);

        sut.greetUser("user@email.com");

        verify(emailGatewayMock, times(1)).sendGreetingEmail("user@email.com");
    }
}
  • 협력자를 mock으로 생성하여 협력자의 메소드(sendGreetingEmail)이 호출 되었는지 확인한다. (통신기반)

    통신이 되었는지 (호출이 되었는지) 검증 한다.

  • 고전파는 통신 기반 스타일보다 상태 기반 스타일을 선호한다.

  • 런던파는 통신 기반 스타일을 선호한다.

  • 두 분파 모두 출력 기반 테스트를 사용한다.

단위 테스트 스타일 비교

회귀 방지와 피드백 속도 지표로 스타일 비교하기

  • 회귀 방지 지표는 특정 스타일에 따라 달라 지지 않는다. 테스트 중 실행되는 코드의 양, 코드 복잡도, 도메인 유의성과 같은 특성으로 결정된다.

  • 테스트 피드백 속도도 상관관계가 거의 없다. 테스트가 프로세스 외부 의존성과 떨어져 단위 테스트 영역에 있는 한, 모든 스타일은 테스트 실행 속도가 거의 동일하다.

리팩토링 내성 지표로 스타일 비교하기

  • 리팩토링 내성은 리팩토링 중에 발생하는 거짓 양성(허위 경보)수에 대한 척도다.

  • 거짓 양성은 식별할 수 있는 동작이 아니라 코드의 구현 세부 사항에 결합된 테스트의 결과다.

출력 기반 테스트

  • 테스트가 테스트 대상 메소드에만 결합되므로 거짓 양성 방지가 가장 우수하다.

출력 기반 테스트가 구현 세부 사항에 결합되는 경우는 테스트 대상 메소드 자체가 구현 세부 사항일 경우일 뿐이다.

상태 기반 테스트

  • 상태 기반 테스트는 거짓 양성이 되기 쉽다.

  • 테스트 대상 메소드 외에도 클래스 상태와 함께 작동한다.

  • 테스트와 제품 코드 간의 결합도가 출력 기반 테스트보다 크기 때문에 구현 세부 사항에 얽매일 가능성이 커진다.

  • 상태 기반 테스트는 큰 API 노출 영역에 의존하므로, 구현 세부 사항과 결합할 가능성도 더 높다.

통신 기반 테스트

  • 허위 경보에 가장 취약하다.

  • 테스트 대역(목)으로 상호 작용을 확인하는 테스트는 대부분 깨지기 쉽다.

    • 스텁과 상호 작용하는 경우다, 이러한 상호 작용을 확인해서는 안된다.
  • 애플리케이션 경계를 넘는 상호 작용을 확인 할 때만 목이 괜찮다.

유지 보수성 지표로 비교하기

  • 유지 보수성은 단위 테스트의 유지비를 측정하며, 다음 두 가지 특성으로 정의한다.
  1. 테스트를 이해하기 얼마나 어려운가 (크기)

  2. 테스트를 실행하기 얼마나 어려운가 (의존성)

  • 테스트가 크면 필요할 때 파악하기도 변경하기도 어려우므로 유지 보수가 어렵다.

  • 프로세스 외부 의존성과 직접 작동하는 테스트는 데이터 베이스 연결, 네트워크 문제 등과 같이 운영하는데 시간이 필요하므로 유지 보수가 어렵다.

출력 기반 테스트

  • 가장 유지 보수성이 뛰어나다.

  • 출력 기반 테스트는 항상 짧고 간결하므로 유지 보수가 쉽다.

이러한 이점은 메소드로 입력을 공급하는 것과 출력을 검증하는 두 가지로 요약할 수 있다는 사실에서 비롯된다.

  • 전역 상태나 내부 상태를 변경할 리 없으므로 프로세스 외부 의존성을 다루지 않는다.

상태 기반 테스트

  • 출력 기반 테스트보다 유지 보수가 어렵다.

  • 상태 검증은 출력 검증보다 더 많은 공간을 차지한다.

통신 기반 테스트

  • 가장 어렵다.

  • 테스트 대역과 상호 작용 검증을 설정해야 하는데, 이는 공간을 많이 차지한다.

출력 기반 테스트를 선호하자.
출력 기반 테스트는 함수형으로 작성된 코드에만 적용할 수 있다.

profile
개발자ㅋ.ㅋ

0개의 댓글