Java와 Junit을 활용한 실용주의 단위 테스트 - Mock테스트 2편

2021, Feb 21    

mock도구를 사용하여 테스트를 단순화하기

stub대신 MOCKITO 사용하기

  • 이전 포스트에서 http, 데이터등 외부서비스로 부터 데이터 응답을 얻어 내는 일을 stub으로 대체 하여 테스트 할 수 있다는 개념을 정리 하였다.
  • 또한 우리의 코드들을 제대로 검증 하려면 그 외부 서비스에 전달 되는 파라미터에 대한 검증이나 응답값에 제대로 전달 되지 않는 예외 사항에 대한 검증도 필요하기 때문에 stub코드에 기능이 추가로 필요하다는 것도 알 수 있었다.
  • 하지만, 여러 예외 적인 케이스의 테스트를 구현하려고 한다면 많은 스텁 구현객체가 필요하게 될 것이다.
  • 이런 반복적인 요소들을 해결해 주는 도구가 spring에 있으니 바로 Mockito가 되겠다. 예제 코드를 통해서 만나보자.

      import static org.mockito.Mockito.*;
    
      Http http = mock(Http.class);
          when(http.get(contains("lat=38.000000&lon=-104.000000")))
          .thenReturn(
                  "{\"address\":{"
                  + "\"house_number\":\"324\","
              // ...
                  + "\"road\":\"North Tejon Street\","
                  + "\"city\":\"Colorado Springs\","
                  + "\"state\":\"Colorado\","
                  + "\"postcode\":\"80903\","
                  + "\"country_code\":\"us\"}"
                  + "}");
          ...
                
    
  • Http http = mock(Http.class); 이부분은 Http mock 객체를 생성하는 부분이다.
  • when() 메소드는 mock객체에 전달 파라미터에 대한 검증 조건을 명시한다. 이전 stub 예제에서 http 전달 파라미터가 요건에 맞지 않았을 경우 오류가 발생 되도록 처리한 부분과 동일하다.
  • thenReturn() 는 메소드를 호출 하여 전달 파리미터에 요구하는 조건이 충족 되었을때 응답값을 설정한다.
  • 이렇게 Mockito를 이용하면 다양한 설정에 대한 stub객체를 여러개 만들지 않고 mock테스트가 가능하다.
  • 또한 응답 객체에 대한 처리 코드를 인증하고자 할때 verfy() 함수를 사용하면 된다. (다음 포스트에서 정리예정)

mock객체 주입하기

  • 위 예제 코드와 같이 테스트 메소드 마다 매번 mock객체를 만들필요 없이, Test클래스에 주입이 가능하다.
     public class AddressRetrieverTest {
         @Mock private Http http;   // (1)
         @InjectMocks private AddressRetriever retriever; // (2)
            
         @Before
         public void createRetriever() {
             retriever = new AddressRetriever();
             MockitoAnnotations.initMocks(this); (3)
         }
    
         ...
     }
    
  • @Mock private Http http; @Mock 애너테이션을 사용하여 mock 인스턴스를 생성한다.
  • @InjectMocks private AddressRetriever retriever; http를 사용하는, 즉 mock 객체를 주입받을 대상(테스트 대상인 클래스) 클래스의 인스턴스 변수를 선언한다.
  • MockitoAnnotations.initMocks(this); this인수는 테스트 클래스 자체를 가르킨다. mockito는 테스트 클래스에서 @Mock 애너테이션이 붙은(예제에서는 http) 필드를 가져와서 각각에 대해 목 인스턴스를 생성한다. 그 다음 @InjectMocks 애너테이션이 붙은 필드에 주입시켜준다.

Mock을 사용할 때 중요한 것

  • Mock을 통해 테스트 하는 것은 실제 동작을 대신한다는 것을 잊지 말아야 한다. 작성한 목 테스트가 제대로 기능을 하는지에 대해서는 아래와 같이 자신에게 몇가지 질문을 할 필요가 있다고 책에서는 말한다.
    • mock이 production 코드(실제 서비스 되는 라이브코드)의 동작을 올바르게 묘사하고 있는가?
    • 프로덕션 코드는 생각하지 못한 다른 형식으로 반환하는가?
    • 프로덕션 코드는 예외를 던지는가?
    • null을 반환하는가?
  • Mock테스트는 프로덕션 코드를 직접 테스트하고 있지는 않다. Mock을 도입하면 테스트 커버리지에서 gap을 형성할 수 있음을 인지해야 한다. 실제 클래스의 종단 간 사용성을 보여 주는 적절한 상위 테스트(종합 테스트)가 있는지 확인 해야 한다.

개인 생각 정리

  • mockito를 통해서 반복되는 mock테스트를 위한 코드를 줄일 수 있다고 하지만, 실제 API나, DB등과의 연결고리를 테스트 할 수 없다는 점에서 보다 촘촘한 테스트를 위해서는 여러 예외사항에 대한 테스트 상황을 만들어야 한다는 점에서, 노가다에서 벗어날 수는 없는 것 같다.
  • 결국 코드를 검정하는 테스트코드는 개발자의 노력과 의지(물론 소속된 개발팀의 분위기)에 달린 것으로 보인다. 실전을 통해서 보다 부딪혀 봐야 겠다.