2. 단위 테스트란 무엇인가

weekbelt·2023년 2월 4일
0

2.1 '단위 테스트'의 정의

런던파

  • 작은 코드 조각을 검증
  • 빠르게 수행
  • 격리된 방식으로 처리

고전파

  • 단일 동작 단위를 검증
  • 빠르게 수행
  • 다른 테스트와 별도로 처리

2.2 단위 테스트의 고전파와 런던파

세번째 속성인 격리 문제의 차이가 고전파와 런던파를 구분할 수 있게 해주는 근원

격리 주체단위의 크기테스트 대역 사용 대상
런던파단위단일 클래스불변 의존성 외 모든 의존성
고전파단위 테스트단일 크랠스 또는 클래스 세트공유 의존성

2.3 고전파와 런던파의 비교

2.3.1 런던파의 특징

  • 입자성이 좋다. 테스트가 세밀해서 한 번에 한 클래스만 확인한다.
  • 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다. 모든 협력자는 테스트 대역으로 대체되기 때문에 테스트 작성 시 걱정할 필요가 없다.
  • 테스트가 실패하면 어떤 기능이 실패했는지 확실히 알 수 있다.

테스트는 코드의 단위를 검증해서는 안 된다. 오히려 동작의 단위, 즉 문제 영역에 의미가 있는 것, 이상적응로는 비즈니스 담당자가 유용하다고 인식할 수 있는 것을 검증해야 한다.

격리 문제에 대한 런던파의 접근

  • 테스트 대상 시스템을 협력자에서 격리하는 것. 즉, 하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역(test double)으로 대체
  • 클래스에 해당하는 단위 테스트 생성

장점

  • 테스트가 실패하면 코드베이스의 어느 부분이 고장났는지 확실히 알 수 있음
  • 테스트 대역을 사용하면 객체 그래프를 다시 만들지 않아도 되고 직접적인 의존성을 대체할 수 있음

예제 코드

  • 재고가 충분히 있을 때만 구매가 성공하는지 검증하는 두 가지 테스트

고전적 스타일로 작성된 테스트

class CustomerClassicTest {

    @Test
    public void purchase_succeeds_when_enough_inventory() {
        // given
        Store store = new Store();
        store.addInventory(Product.SHAMPOO, 10);
        Customer customer = new Customer();

        // when
        boolean success = customer.purchase(store, Product.SHAMPOO, 5);

        // then
        assertThat(success).isTrue();
        assertThat(5).isEqualTo(store.getInventory(Product.SHAMPOO));
    }

    @Test
    public void purchase_fails_when_not_enough_inventory() {
        // given
        Store store = new Store();
        store.addInventory(Product.SHAMPOO, 10);
        Customer customer = new Customer();

        // when
        boolean success = customer.purchase(store, Product.SHAMPOO, 15);

        // then
        assertThat(success).isFalse();
    }
}

이 코드는 단위 테스트의 곡전 스타일 예로, 테스트는 협력자(Store 클래스)를 대체하지 않고 운영용 인스턴스를 사용하기 때문에 Customer와 Store 둘 다 효과적으로 검증한다. 하지만 Customer가 올바르게 작동하더라도 Customer에 영향을 미치는 Store 내부에 버그가 있으면 단위 테스트에 실패할 수 있다. 결국 테스트에서 두 클래스는 서로 격리돼 있지 않다.

런던 스타일로 작성된 단위 테스트

@ExtendWith(MockitoExtension.class)
class CustomerLondonTest {

    @Mock
    private Store store;

    @Test
    public void purchase_succeeds_when_enough_inventory() {
        // given
        given(store.hasEnoughInventory(Product.SHAMPOO, 5)).willReturn(true);
        Customer customer = new Customer();

        // when
        boolean success = customer.purchase(store, Product.SHAMPOO, 5);

        // then
        assertThat(success).isTrue();
        then(store)
            .should(times(1))
            .removeInventory(Product.SHAMPOO, 5);
    }

    @Test
    public void purchase_fails_when_not_enough_inventory() {
        // given
        given(store.hasEnoughInventory(Product.SHAMPOO, 5)).willReturn(false);
        Customer customer = new Customer();

        // when
        boolean success = customer.purchase(store, Product.SHAMPOO, 5);

        // then
        assertThat(success).isFalse();
        then(store)
            .should(times(0))
            .removeInventory(Product.SHAMPOO, 5);
    }
}

런던스타일의 테스트는 Store의 실제 인스턴스를 생성하지 않고 Mockito를 사용해 Mock객체를 생성하였다. 또한 샴푸 재고를 추가해 Store 상태를 수정하는 대신 hasEnoughInventory() 메서드 호출에 어떻게 응답하는지 목에 직접 정의 한다. 사실 Store가 구체클래스인데 구체 클래스를 목으로 만드는것은 안티패턴 이지만 런던 스타일의 테스트 코드는 어떤지 고전파 스타일과 비교하기위해서 이렇게 작성하였다.

참고

profile
백엔드 개발자 입니다

0개의 댓글