[tdd] 상태검증과 행위검증, stub과 mock 차이

  • SUT(System Under Test) : 주요 객체(primary object)
  • 협력객체(collaborator) : 부차적 객체(secondary objects)
  • 테스트 더블(Test Double) : 테스팅을 목적으로 진짜 객체대신 사용되는 모든 종류의 위장 객체
    • Dummy, Fake Object, Stub, Mock

상태검증 vs 행위검증

https://minslovey.tistory.com/97

  • 상태검증은 메서드가 수행된 후 SUT나 협력객체의 상태를 살펴봄으로써 올바로 동작했는지를 판단하게 된다

    1
    2
    3
    4
    SomeClass someClass = new SomeClass();
    someClass.someMethod();

    assertThat(someMethod.someStatus()).isEqualTo(true);

    someStatus값이 true인지 상태 검사

  • 행위검증은 상태검증과는 다르게 SUT가 협력객체의 특정 메서드가 호출되었지 등의 행위를 검사함으로써 올바로 동작했는지 판단하게 된다

    1
    2
    3
    SomeClass someClass = new SomeClass();

    verify(someClass).someMethod();

    someClass의 someMethod가 실행되었는지 행위 검사

stub vs mock

http://testing.jabberstory.net/

많은 테스트 더블들이 있지만, 테스트 더블들의 역할이 딱딱 나뉘어져 있지도 않고, 서로가 서로의 특성을 조금씩 포함하므로, 대표적으로 stub과 mock만을 구분한다.

  • stub

    • 호출이되면 미리 준비된 답변으로 응답하는 것
    • 테스트시에 프로그램된 것 외에는 응답하지 않는다
    • 협력객체의 특정 부분이 테스트하기 힘들 경우 stub을 사용하면 수월하게 테스트할 수 있다
    • 일반적으로 우리가 mock으로 잘못 알고있다
  • mock

    • 다른 테스트더블과는 다르게 행위검증 사용을 추구한다
    • 행위를 기록하는 식의 로직이 들어가있겠지…

SUT가 실제 협력객체와 대화하고 있다고 믿게해야하므로, 모든 테스트 더블들의 동작이나 형태는 같다.
하지만 여기서 mock은, 행위검증을 추구한다는 것 자체가 다른 테스트 더블과의 큰 차이점이다.

classicist, mockist

  • classicist들은 가능하면 항상 진짜 객체를 사용하고, 진짜 객체를 사용하기 만만치 않으면 더블을 사용한다
    • 그리고 항상 상태검증을 사용하려고 한다
  • mockist들은 관심있는 행위를 가진 모든 객체에 모의객체를 사용하려고 한다
    • 그리고 항상 행위검증을 사용하려고 한다 향

간단한 협력과 간단하지 않은 협력에서의 둘의 선택

  • 간단한 협력
    • classicist라면 실제 객체를 사용해 상태검증을 할 것이다
    • mockist라면 mock을 쓰고 행위검증을 할 것이다
  • 간단하지 않은 협력
    • mockist라면 당연히 mock을 쓰고 행위검증을 사용한다. 간단하지 않은 협력에서 mock의 장점이 부각(?)되기 때문이다
    • classicist라면 상황에 맞춰 가장 쉬운 방법을 사용하려 한다. 테스트 더블이 필요하다면 테스트 더블을 사용할 것이다.

언제 상태검증? 언제 행위검증?

현재까지 이해한 바로 작성해보면,

행위검증의 경우 특정 메서드의 호출과 같은 것을 검증하기 때문에, 구현에 굉장히 의존적이게 된다
이 말인 즉 프로덕션 코드가 변경되면 테스트코드가 변경될 확률이 높아진다는 것을 의미한다
테스트는 그것을 작성하면서 설계에 피드백을 받을 수 있다는 장점이 있는데(테스트가 어렵다면 설계에 문제가 있는것은 아닌지 의심해보는 과정)
행위검증의 경우 상태검증보다 테스트 작성이 쉬워서 이런 피드백을 받을 기회(?)가 많이 없어지게 된다

강력한 행위검증 라이브러리(powermock)같은 것을 쓰게 되면 이런 부분을 놓치게 될 가능성이 더 크다
예를 들어 powermock의 경우 private 메서드도 테스트할 수 있고, static 클래스도 주입할 수 있는 등 강력한데, 이러한 특징때문에 우리는 설계가 잘못되었다는 의심을 하지 않고 지나갈 확률이 커진다

상태검증의 경우 상태를 검증하기 위해 상태를 노출하는 메서드가 많이 추가될 수 있다

mockist들은 이것을 큰 요소로 여긴다

이러한 이유로 대체적이면 상태검증을 사용하는것이 좋다

하지만 상태검증을 하기 힘든 경우들이 종종 있다
예를 들면 알람 같은 것이다
이런 테스트의 경우, 통합 테스트를 고려해보는 것도 좋다
(알람을 쏘고 로그를 읽어와서 상태검증을 할 수도 있다)
근데 또 이게 단점이 많은게,
전체적인 테스트 시간이 늘어나고(피드백을 받는 시간이 늘어난다), 문제가 발생했을때 찾기가 쉽지 않아진다
SUT가 아닌 다른곳에 의존성이 생길수 있다

이러한 이유 때문에 행위검증을 선택하는 경우도 종종 있다
구현에 의존적이라는 행위검증의 특징을 안고 가는것이 좀 별로긴 하지만, mockito같은 라이브러리를 적절히 사용하고, 테스트를 잘 구성하면 이러한 단점도 어느정도 커버할 수 있다고 한다