-
Notifications
You must be signed in to change notification settings - Fork 11
Mock 사용방식
Mock library 사용방식은 유명인들 사이에서도 의견이 많이 엇갈려보입니다.
Mockito 지지자들은 기존의 expect-run-verify 방식이 깨어지기 쉽고, TDD의 자연스러운 사고와 잘 아올리지 않는다고 주장합니다. (그런데 '복잡하다’나 '자연스럽다’는 정말 주관적인 표현이기는 합니다 ^^ㅣ)
아래 링크에 간략하게 설정되어 있습니다.
Mockito의 접근방식은 stub, run, assert가 주된 흐름입니다. 두 차이는 행위기반 검증과 상태기반 검증으로 구분되기도 합니다. 앞에서도 설명하셨듯이, Mockto에서는 verify는 선택이고, stub자체에 암묵적으로 verify가 포함되어 있다고 여기기도합니다. 그리고 Mockito는 Mock과 Strub의 개념을 명확히 구분하는데 반해서 다른 라이브러리는 그것이 섞여있다고 주장합니다. (Stub는 그냥 정해진 값만 return하는 것이고 Mock은 verify기능까지 포함하는 개념입니다.) 그래서 Mockto를 쓸 때는 verify와 stub가 중복된다고 생각되면 verify는 꼭 하지 않아도 된다고 Mockito 반드신 분이 그러셨습니다. ( http://monkeyisland.pl/2008/04/26/asking-and-telling/ )
반면 행위기반의 검증이 객체 사이의 메시지 교환을 검증하는데 더 적합한데 Mockto는 그걸 도외시한다는 주장도 있습니다.
저도 이전에는 대부분의 상황에 상태검증이 더 좋다는 생각이 강했으나, 점점 두 가지의 방식은 젓가락과 포크처럼 모두 다 들고 다니면 다 잘 쓴만한 곳이 있어서 그때그때 고민해야한다는, 좀 두리뭉실한 입장으로 바뀌었습니다. 특히 mock이나 테스트 대상객체가 return type이 void일 경우에는 상태검증을 하지 않으면 검증조건이 실제 코드와 좀 거리가 멀어지는 '뒷문 검증’이 되기 쉬워집니다.
결국 테스트의 초점을 어디에 두는냐에 따라서 전략은 달라지는데, 테스트 대상 객체에서 mock에다 값을 잘 전달했는지에 초점을 맞춘다면 reuturn value에 대한 assert없이 verify하는 상태검증만 하는 것이 초점이 명확해 지지 않나 합니다.
아래 예제에 코드에 너무 로직이 없어서 약간 수정을 했는데, service 객체에 선언된 두번째,세번째 파라미터인 page와 PageSize 를 곱해서 해서 repository 클래스에 넘긴다고 하면
public List<ScheduleManagement> getScheduleList(int artistId, int page, int pageSize) throws Exception {
return scheduleRepository.selectScheduleManagementList(artistId, page * pageSize);
}어떤 파라미터가 전달되었는지만 검증한다면 아래처럼 verify만 해볼만도 합니다.
public void testGetScheduleList() throws Exception {
// 실행
List<ScheduleManagement> actualList = service.getScheduleList(1, 4, 10);
// 검증
verify(repository).selectScheduleManagementList(1, 40);
}이걸 stubing을 해서 return value로 검증을 한다면 당연히 비어있지 않은 List를 넘겨서, 의도한 값이 mock에 전달되었음을 간접적으로 검증할 수 있겠죠.
public void testGetScheduleList() throws Exception {
// 기대값 설정
List<ScheduleManagement> expectList = Arrays.asList(new ScheduleManagement());
when(repository.selectScheduleManagementList(1, 40)).thenReturn(expectList);
// 실행
List<ScheduleManagement> actualList = service.getScheduleList(1, 4, 10);
// 검증
assertEquals(expectList, actualList);
}
---
만약 그런데 repository에서 빈 list를 return할 때 특별한 처리 로직이 있다면, 또 다른 검증방식을 생각해야겠습니다.
암튼 결론은 하나의 메소드안에서는 테스트 초점을 좁힌다면 효율적이고 의도가 잘 드러나는 테스트가 되고, 이게 default value때문에 나온 값인지, when, then으로 Stubbing한 값인지 헷갈리는 경우도 잘 없지 않을까합니다. 호출이 되지 않는 경우를 검증하려면 verify~never 같은 것도 활용할 수 있고요.
그리고 제 의견으로는 mock에서 다시 mock을 return하는 경우는 단 좋은 신호로 봅니다. demeter의 법칙을 어긴 호출 (user.getAddress().getZipNo() 처럼 줄줄이 엮인 호출) 등 dependency가 부적한 코드에서 그런 예제가 많았고, 대부분 production code를 개선하라는 신호로 여길만합니다. 그래서 어쩔 수 없을 경우만 Mock에서 다시 Mock을 return해야 된다고 봅니다.
Collection을 Stubing이나 verify를 하는 목적이 아니고, 단순히 equals에서 디폴트인 빈 Collection과 구분하기 위한 용도라면 그냥 테스트 데이터를 담은 Collection을 생성하는 것이 더 의도가 잘 표현된다고 생각합니다.