본문 바로가기
개발관련 서적 정리/Unit Testing

5. 목과 테스트 취약성

by Backchus 2023. 2. 6.

5.1 목과 스텁 구분

5.1.1 테스트 대역 유형

  • 목은 외부로 나가는 상호 작용을 모방하고 검사하는 데 도움이 된다. 이러한 상호 작용은 SUT가 상태를 변경하기 위한 의존성을 호출하는 것에 해당한다.

  • 스텁은 내부로 들어오는 상호 작용을 모방하는 데 도움이 된다. 이러한 상호 작용은 SUT가 입력 데이터를 얻기 위한 의존성을 호출하는 것에 해당한다

    5.1.2 도구로서의 목과 테스트 대역으로서의 목

    class ControllerTest {
    
      @Mock
      private IEmailGateway emailGateway;        // mock 생성
    
      @Mock
      private IDatabase database;                // mock 생성
    
      @Test
      public void sending_a_greetings_email() {
          // given
          Controller sut = new Controller(emailGateway, database);
    
          // when
          sut.greetUser("user@gamil.com");
    
          // then
          then(emailGateway)
              .should(times(1))
              .sendGreetingEmail("user@email.com");    // 테스트 대역으로 하는 SUT의 호출을 검사
      }
    
      @Test
      public void creating_a_report() {
          // given
          given(database.getNumberOfUsers()).willReturn(10);        // stub 생성
          Controller sut = new Controller(emailGateway, database);
    
          // when
          Report report = sut.createReport();
    
          // then
          assertThat(10).isEqualTo(report.getNumberOfUsers());
      }
    

}

### 5.1.3 스텁으로 상호 작용을 검증하지 말라
- SUT에서 스텁으로의 호출은 SUT가 생성하는 최종 결과가 아니라 최종 결과를 산출하기 위한 수단일 뿐이다.

> 스텁과의 상호 작용을 검증하는 것은 취약한 테스트를 야기하는 일반적인 안티 패턴이다.

### 5.1.4 목과 스텁 함께 쓰기
```java
    @Test
    public void purchase_fails_when_not_enough_inventory() {
        // given
        Store storeMock = mock(Store.class);
        given(storeMock.hasEnoughInventory(Product.SHAMPOO, 5)).willReturn(false);        // 준비된 응답을 설정
        Customer sut = new Customer();

        // when
        boolean success = sut.purchase(storeMock, Product.SHAMPOO, 5);

        // then
        assertThat(success).isFalse();
        then(storeMock).should(times(0)).removeInventory(Product.SHAMPOO, 5);    // SUT에서 수행한 호출을 검사
    }

5.1.5 목과 스텁은 명령과 조회에 어떻게 관려돼 있는가?

  • CQS(Command Query Separation)원칙에 따르면 모든 메서드는 명령이거나 조회여야 하며, 이 둘을 혼용해서는 안 된다.
  • 메서드가 사이드 이펙트를 일으키면 해당 메서드의 반환타입이 void인지 확인하라. 그리고 메서드가 값을 반환하면 사이드 이펙트가 없어야 한다.
  • 가능한한 CQS원칙을 따라라.

    5.2 식별할 수 있는 동작과 구현 세부 사항

  • 테스트 코드와 코드의 구현 세부사항의 강결합을 피하는 방법은 코드가 생성하는 최종 결과(식별할 수 있는 동작)를 검증하고 구현 세부 사항과 테스트를 가능한 한 떨어뜨리는 것뿐이다.
  • 구현 세부 사항을 숨기면 클라이언트의 시야에서 클래스 내부를 가릴 수 있기 때문에 내부를 손상시킬 위험이 적다.
  • 데이터와 연산을 결합하면 해당 연산이 클래스의 불변성을 위반하지 않도록 할 수 있다.
  • 모든 구현 세부 사항을 비공개로 하면 테스트가 식별할 수 있는 동작을 검증하는 것 외에는 다른 선택지가 없으며, 이로 인해 리팩터링 내성도 자동으로 좋아진다.

참고