개발

단위 테스트에서 Mockito 잘 활용하기

오렌지색 귤 2023. 9. 22. 23:27
반응형

서론

 

단위 테스트는 두 가지 견해 고전파와 런던파로 구분할 수 있다.


고전파는 켄트 백의 테스트 주도 개발(TDD)으로 원론적으로 접근하는 방식 때문에 '고전'이라 한다.


런던파는 런던 프로그래밍 커뮤니티에서 시작되어, 목 추종자라고도 불린다.

 

아래 글에서는 위의 학파와는 상관없이 단순히 단위 테스트에서 mockito를 잘 활용하는 방법에 대해서만 설명할 예정이다.

 

 

 

단위 테스트의 관심사

 

controller
👇
(interface) service
👇
service implementation (복잡한 비지니스 규칙)
👇
(interface) repository
👇
repository implementation (외부 의존성)

 

위의 흐름에서 단위 테스트 자체는 인터페이스의 실제 구현에 전혀 관심이 없다.

 

 

 

왜 단위 테스트에서 mock을 사용해야 하는가?

 

서비스 구현이 데이터베이스나 다른 타사 시스템에 종속되어 있고, 서비스의 기능을 테스트하는 상황을 상상해보자.


현재 우리가 만들고자 하는 테스트는 데이터베이스에 관한 테스트가 아니다.


그러므로 외부 의존성에 대해서는 적절하게 mock 객체를 활용하는 것이 좋다.

 

테스트에 더 많은 종속성을 추가할수록 테스트가 다른 이유로 인해 실패할 확률이 커진다.

 

(어이없는) 실패를 줄이는 것 외에도 테스트의 복잡성을 줄여서 약간의 노력을 덜어준다.

 

 

 

일반적인 단위 테스트 작성 과정

 

  • 테스트 중인 유닛 외부에 있는 몇 가지 클래스를 모킹
  • 테스트 중인 유닛 호출
  • 유닛에서 반환된 데이터 검증 + 모의 동작에 대한 호출 확인 + 예상되는 예외 테스트

 

 

테스트 더블

 

  • 테스트 시, 실제 객체 대신 사용하는 단순한 객체


오른쪽으로 갈 수록 테스트 생성이 어려워지므로 적절한 테스트 객체를 선택하자.

 

Dummy

  • 객체가 필요하지만 내부 기능이 필요하지는 않을 때 사용
  • ex) 사용하지 않는데 파라미터로 전달은 해야되는 객체

 

Fake


  • 실제로 사용된 객체는 아니지만 같은 동작을 하는 구현된 객체
  • 프로덕션에서는 사용할 수 없다

 

Stub


  • Dummy 데이터가 실제로 동작하도록 만들어둔 객체
  • 미리 정의된 데이터를 보유하고 테스트 호출 시에 응답하도록 사용되는 객체
  • 테스트를 위해 프로그래밍 되었으며, 이외의 항목에는 응답하지 않음
  • ex) when().thenReturn()

 

Spy

  • Stub 역할을 하면서 약간의 정보를 더 기록
  • 예상된 메서드가 잘 호출되었는지, 몇 번이나 호출되었는지 등으로 사용

 

Mock

  • 행위를 검증하기 위해 가짜 객체를 만들고 테스트하는 방법
    (Stub은 상태를 검증할 때 사용한다)
  • 행위 기반 테스트는 복잡도나 정확성 등이 상태 기반 테스트보다 어려운 부분이 많으므로 상태 기반 테스트가 가능하다면 만들지 말자

 

 

언제 사용해야 하는가?

 

  • 격리된 테스트를 위해
    • 외부 종속성의 동작에 의존하지 않고 해당 동작을 확인할 수 있다

 

  • 구현이 아닌 동작 확인
    • 메서드를 테스트 할 때는 구현 세부 사항보다는 메서드의 동작에 초점을 맞추는 것이 중요하다

 

 

Better Practice

 

1. VerificationMode는 구체적일수록 좋다

 

  • Mockito.verify(myMock, atLeastOnce()).myMockBehavior()
  • Mockito.verify(myMock, times(n)).myMockBehavior()

 

당연하게도 후자가 더 구체적이다.


atLeast(), atMost(), atLeastOnce(), atMostOnce() 등은 사용을 피하자.

 

 

2. Matcher도 구체적일수록 좋다

 

  • Mockito.verify(service,times(1)).save(any(User.class))
  • Mockito.verify(service,times(1)).save(eq(user))

 

당연하게도 후자가 더 구체적이다.


Object의 동등성까지 비교한다면, save() 메서드에서 user의 상태가 변경되는 상황도 테스트에서 검출할 수 있다.

 

 

3. 객체 상태 검증을 위해서 ArgumentCaptor를 사용하라

 

ArgumentCaptor 는 모킹된 행위로 넘겨지는 파라미터를 캡쳐해준다.


이러한 인수들에 대한 값으로 검증을 하면 된다.

 

ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class);
User expectedUserForSave = userCaptor.getValue();
// call the method to be tested.
assert(‘abc@def.com’,user.getEmail());

 

 

4. 중요한 exception는 반드시 테스트하기

 

예상되는 예외가 테스트 상황에서 올바르게 던져지는지 테스트하도록 하자.

 

Mockito에서 에러를 던지도록 할 수도 있다

 

Mockito.when(repository.find(expected.getId()))
   .thenThrow(RuntimeException.class);

 

 

5. 테스트 간에 서로 영향을 미치지 않도록 하라

 

  • 목 객체를 공유하지 마라
    • Reset() 사용을 피하고, @BeforeEach() 등을 활용하자

 

6. 객체나 컬렉션은 실제 값을 사용하라

 

값 객체는 논리가 없으므로 모킹할 이유가 없다.

 

 

7. 구체 클래스를 모킹하는 것을 피하라

 

모킹하려는 클래스의 특정 구현에 얽매이게 된다.

 

 

8. 긴밀하게 결합된 코드에 대해서는 모킹을 피하라

 

모킹은 분리된 코드에서 가장 잘 작동한다.

 

 

9. Spring Integration Test와 함께 사용하지 마라

 

싱글턴 객체가 목 객체에 의해 수정되는 이상한 부작용이 생길 수 있다.


또한 mockito test의 가장 큰 장점 중 하나인 속도 또한 느려진다.

 

(레거시 코드에서 흔히 존재하는) 긴밀하게 결합된 코드의 경우, 두 개 이상의 클래스가 하나의 단위를 형성하는 경우가 있어, 이는 여전히 단위 테스트로 테스트 될 수 있다.

 

그렇지 않은 경우 쌍을 이루는 클래스에 자체 논리가 많은지, 별도로 테스트하는 것이 중요한지 생각해보라.

 

 

10. 테스트하는 모델은 모킹하지 마라

 

당연한 말씀

 

 

11. 모킹을 남용하지 마라

 

때로는 "as-is" (있는 그대로) 가 낫다.

 

 

12. Servlet api를 직접 모킹하지 마라

 

org.springframework.mock.web 패키지를 사용하자

 

 

13. Assert를 Verify로 대체하지 마라

 

단위테스트에서는 동작으로 인한 결과를 중점적으로 테스트 해야한다.


verify 만으로는 "이러한~ 저러한~ 메서드를 실행한다" 이상을 검증할 수 없다. (추후 수정될 수 있는)

 

 

참고

 

 

Example Codes

 

https://www.toptal.com/java/a-guide-to-everyday-mockito

 

 

장점

  • void 메서드 검증
    • verify 사용

 

  • 속도, 성능, 개발 편의성

 

 

단점

  • @MockBean , @SpyBean 등을 많이 사용하면 Spring Boot가 각 테스트에 대해 새로운 애플리케이션 컨텍스트를 생성하기 때문에 테스트 실행 시간이 길어진다.

 

  • 모든 것을 모킹할 수는 없다
    • final 이나 static 메서드, equals(), 생성자 등을 모킹해야 한다면 PowerMock을 사용하자

 

 

마지막 한줄평

버그를 발견했을 때는 오히려 코드를 깨기 위해 더 많은 테스트를 추가하자.

 

 

 

참조

https://medium.com/@samuelcatalano/best-practices-for-java-testing-with-mockito-56532ee16918
https://reflectoring.io/spring-boot-mock/#why-mock
https://medium.com/@ketannabera/junit-and-mockito-best-practices-dos-and-don-ts-9b29ce38abff
https://java2blog.com/mockito-example/
https://reflectoring.io/clean-unit-tests-with-mockito/
https://www.diffblue.com/blog/testing/mocking-best-practices/
https://mestachs.wordpress.com/2012/07/09/mockito-best-practices/
http://hamletdarcy.blogspot.com/2010/09/mockito-pros-cons-and-best-practices.html
https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da

반응형