Mock을 마주하는 자세
Stunt Double 이 아닌 Test Double
용어 정리
마틴 파울러의 MocksArentStubs 아티클과 함께 알아보는 Mock과 Stub 의 차이
예시로 살펴보기
"In both cases I'm using a test double instead of the real mail service. There is a difference in that the**************************** stub uses state verification while the mock uses behavior verification."
"Using mocks this test would look quite different."
"We can then use state verification on the stub like this."
Mock 은 행동에 대한 결과가 어떠한 값을 반환해야 하는지를 검증하게 되며, 위 Mock 예시의 테스트 코드에서 실제 모킹된 MailService가 send() 라는 메서드를 호출이 되었는지 메서드 호출 여부에 대해 검증을 한다.
⇒ 호출 여부에 대한 검증을 한다는 것은 객체가 특정 행동을 정말 수행 했는가? 를 중점적으로 둔다는 것이다.
"사용자가 회원가입을 하면 회원가입 축하 메일을 보내는 상황을 테스트 한다면?"
AuthService 의 signUp() 라는 메서드를 호출하면 EventListner를 통해 메일을 발송하고 있다고 가정한다.
그렇다면 실제 MailService 에 createAccountUser() 라는 메서드가 이벤트에 의해 정말 호출이 되었는지 테스트를 해볼 수 있는데, 이 때 Mock 객체를 이용한 테스트를 활용하는 것이다.
Stub 은 Stub 객체의 메서드를 수행한 후, 객체가 갖고 있는 상태값이 메서드에 의해 제대로 변경 되었는지 검증하는 것이다.
"사용자가 게시글을 읽으면 조회수가 1 증가 해야 한다고 하는 상황을 테스트 한다면?"
ArticleService 의 read() 라는 메서드를 호출한 후 viewCount 가 1이 되었다 라는 코드를 작성하면, 이 테스트는 Stub 객체를 이용한 테스트인 것이다.
결론적으로 모킹해오는 객체들은 stubbing을 해주지 않는다면 기본 값들을 반환하는 정책을 따르며, 테스트하려는 대상에만 집중하고 내가 제어할 수 없는 외부 세계의 영역을 stubbing(mocking) 한다.
이토록 어렵게 느껴지는 Mock 과 Stub 의 차이는 정의의 내용이 우리를 혼란스럽게 할 뿐, 실질적으로 용어를 사용할 땐 대부분 Mock 이라는 단어로 포괄하여 사용한다.
굳이 굳이, mock 과 stub 을 엄밀히 구분해가며 소통하지 않으니 개념적으로만 이해할 요소라고 생각이 든다.
Mockito 어노테이션 여행기
"Mock 객체의 특정 메서드를 Stubbing 하였지만, 메서드 내부에서 다른 메서드를 호출 하는 경우 어떻게 될까?"
위 코드를 보면, MailServiceTest 에서 MailSendClient 의 sendMail() 을 Stubbing 하여 실제 사용자에게 보내는 메일을 보내지 않고 테스트 하기 위한 코드를 작성했다.
하지만, 내부적으로 호출 되고 있는 mailSendHistoryRepository 의 save() 메서드는 어떻게 될까?
메일 발송 기록을 저장하는 코드인데, 이 내용은 테스트 할 때 마다 저장되면 안되기 때문에 똑같이 Stubbing 을 해줘야하는걸까?
Stubbing 메서드 내부에서 실행되는 다른 메서드 디버깅하기

디버깅을 해보면 save() 메서드의 결과값이 null 인 것을 알 수 있고, 실제 저장이 되지 않았다.
Mock 객체 내부 코드를 살펴보면, 생성할 때 기본값을 사용할 수 있도록 유도하며 기본 값을 반환하게 된다.

BDD Given 절에서 Mockito.when 을 쓰며 어색함을 느끼지 못하였다
테스트 케이스를 작성할 때 현재까지 BDD 방식을 따라왔다. 그렇기에 Mock 객체 생성 또는 Stubbing 과정을 테스트를 위한 준비 과정으로써 Given 에 속하는 것이 맞다.
하지만, Mockito 의 Stubbing 메서드 명칭은 Mockito.when 이다. 테스트 코드도 하나의 문서 처럼 활용할 수 있기 때문에 읽는이로 하여금 오해를 살 수 있는 부분이된다.
역시 소프트웨어 세계는 대부분 누군가 했던 고민의 흔적이 있듯, 이런 Mockito 라이브러리를 그대로 래핑하여 BDDMockito 라는 라이브러리를 제공하고, 위 코드에서 볼 수 있듯이 given 이라는 명칭을 쓴다.
모든 동작은 Mockito 와 같지만 더 자연스러운 읽는 흐름을 갖을 수 있기 때문에, 앞으로 Given 절에서 Stubbing 할 땐 BDDMockito 로 꾸미지 않았지만 꾸민 느낌을 주어 편안함을 제공해보자.
당신은 Classicist 인가요? Mockist 인가요?
이 두 입장 간의 차이는 개발자간의 성향에서 나타나겠지만 나는 개인적으로 클래시스트이다. 테스트 코드를 작성하는 이유는 현재 기능에 대한 빠른 테스트가 아닌 전체적인 서비스에 대한 신뢰성이라고 생각하는데, 이 또한 반대 입장의 모키스트의 얘기를 들어보면 모호한 부분이 있을 것이다.
그 이유는 아무래도 테스트 코드 작성만으로 서비스의 신뢰성을 모두 보장할 수 있는가? 와 밀접한 주제일 것 같고, 그 외에는 매번 테스트할 때 마다 드는 시간적 비용이 우리 서비스 코드를 작성하는 데 뺏기고 있다 라는 것이 될 것 같다.
그럼에도 불구하고 클래시스트인 이유는 Mocking 한 계층의 서비스 코드 변경 사항이 테스트 코드를 업데이트 하지 않은 채 프로덕션에 반영하려고 CI 를 돌려보면 통과하지만, 사용자의 행동은 우리가 의도했던 바와 다르게 동작할 수 있다는 이유만으로 개인적으로 클래시스트 입장에서 테스트 코드를 작성한다.
"그럼 어떨 때 Mocking 을 해야할까?"
Mocking 이 필요한 부분은 우리 시스템 코드가 아닌, 외부에 영향을 받고 있는 시스템들을 사용중인 메서드가 있다면 단언컨대 이 때 필요하다 라고 말할 수 있다.
외부 메일 서버를 이용하여 사용자에게 메일을 발송하거나, 외부 인증/인가 서버를 통해 사용자 정보를 받아오거나 등 외부 시스템에서 발생한 장애는 우리 시스템에서 수정할 수 없고, 제어권이 없기 때문이다.
Last updated