93호. Mock 보다 Spy free

2019-12-12

안녕하세요. 테스트 많이들 하고 계시지요? :) 당장 업무에 테스트를 사용하고 있지 않더라도 잊지는 말아요 우리 ㅠ 근데 갑자기 구독자 분들 중 실무에 테스트를 사용하시는(부분적으로라도) 비율이 얼마나 되는지 궁금하네요. 테스트 사용 여부를 회신해주시면 앞으로 뉴스레터를 만드는데 잘 참고하겠습니다.


오늘의 주제는 Mock과 Spy입니다. 최근에 Mock을 Spy로 대체하기(Replacing Mocks with Spies)를 읽고 앞으로 Mock 보다 Spy를 써야겠다고 생각했는데요, 내용을 간략히 정리해 봤습니다.


Arrange, Act, Assert


전통적으로 테스트는 Arrange, Act, Assert 3단계로 구성합니다. 테스트에 필요한 데이터나 상황을 준비하고(Arrange), 테스트하고자 하는 동작을 수행하고(Act), 원하는 결과가 나왔는지 검증하는(Assert) 코드를 순서대로 작성하는 것이지요.


Mock과 Spy


Mock은 객체를 흉내내서 객체가 어떻게 "동작해야 하는지" 미리 정의하고, 그대로 "행동하지 않으면" 테스트가 실패하도록 합니다.


Spy도 일종의 Mock인데, 객체가 어떻게 "동작했는지를" 확인하고, 그대로 "행동하지 않았으면" 테스트가 실패하도록 합니다.


다음은 Mockery 매뉴얼의 Mock 예제를 조금 가공한 코드입니다.


// Arrange 
$testDouble = \Mockery::mock('MyClass');
$testDouble->shouldReceive('foo');

// Act
$targetObj = new TargetObj($testDouble);
$targetObj->handle();

// Assert

$testDouble->shouldReceive('foo')는 MyClass의 목객체가 반드시 foo 매서드를 실행해야 한다고 정의하는 것입니다. 따라서 $targetObj->handle()이 처리되는 동안 TargetObj에 주입된 MyClass의 목객체의 foo()가 호출되지 않으면 테스트는 실패합니다. 어설션 코드를 한 줄도 작성하지 않아도 말이지요.


다음은 Mock 대신 Spy를 사용한 예제입니다.


// Arrange
$spy = \Mockery::spy('MyClass');

// Act
$targetObj = new TargetObj($spy);
$targetObj->handle();

// Assert
$spy->shouldHaveReceived()->foo();

두 예제를 보시면 아시겠지만, Mock은 어설션이 Arrange 영역에 정의되고, Spy는 Assert 영역에 정의됩니다. Mock을 Spy로 대체하기 글의 저자인 아담 와든은 테스트가 예제처럼 단순할 때는 상관없지만 테스트가 길고 복잡해질수록 어설션이 한 곳에 모여있는게 코드를 파악하기 더 좋다고 합니다. 따라서 어설션을 Assert 영역에 작성하는 Spy를 선호한다고 하네요.


Spy의 단점


Mock은 미리 정해주지 않은 매서드가 호출되면 테스트가 실패합니다. 반면 Spy는 어떤 매서드 호출도 다 허용합니다. 테스트 환경을 완벽하게 통제하지 못한다는 점에서 단점이라고 할 수도 있지만, 아담 와든은 Mock을 100% Spy로 대체해도 큰 문제가 없었다고 합니다.


마치며


서두에 말씀드렸듯 저도 앞으로 Mock을 써야할 일이 있으면 Spy를 써볼까 합니다. 써보다가 이슈가 있거나 하면 다시 소식 전해드리게요. 테스트를 실무에 사용하고 있는지 회신 부탁드릴게요. 미리 감사합니다. :)


1일 1식 라라벨 93호

2019년 12월 12일


이현석

메쉬 코리아 개발자. 바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서를 쓰고, 클린 아키텍처 인 PHP를 번역했습니다. 2020년에 출간될 Laravel Up & Running 2nd Edition을 번역하고 있습니다.