안녕하세요. 렌입니다.
오늘은 우리가 테스트 코드 작성할 때마다 Mockito 을 사용하는데, 왜 사용하는지
대해서 조금 더 깊게 이야기해볼까 합니다.
테스트하고자 하는 객체의 의존성을 격리한다.
라는 것이 우리가 흔히 아는 왜 사용하는지
에 대한 답인데요.
여기서 Mocktio 는 Test Double
라는 큰 개념 안에 있는 하나입니다.
Test Double
에 대해서 알게 된다면 Mocktio 을 왜 사용하는지 조금은 이해할 수 있지 않을까요?
When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC), we can replace it with a Test Double.
The Test Double doesn’t have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one!
테스트 코드를 작성할 때 실제 DOC(depoended-on component; 의존 구성 요소)를 사용할 수 없다면, DOC 대신 테스트 더블로 대체할 수 있음.
테스트 더블은 실제 DOC와 똑같이 행동하지 않아도 되며, 똑같은 API만 제공하면 된다.
Gerard Meszaros
Test Double 의 종류
어떤 Test Double
이 있을까요? 흔히 4가지를 말합니다.
- Stub
- Fake
- Spy
- Mock
Stub
Stub 이라는 말을 어디서 들어봤을까요? Stub 의 뜻은 끝이 잘렸거나 유난히 짫은 것
이라는 의미를 가집니다.
Stub 의 목적은 원래의 구현을 최대한 단순한 것으로 대체하는 것입니다. 예를 들면, 어떤 객체의 기본값을 반환하는 등의 행위를 의미할 수 있습니다.
Stub 이란 테스트 더블은 최대한 단순하게 만들어 테스트 격리를 유도하거나 최소한의 행위만을 보장하는 객체을 의미하는 것이라 생각했습니다.
뒤에서 설명할 Fake, Mock 과 비슷하다고 생각할 수 있지만, 적어도 제가 생각하기에는 테스트가 가진 기능이 가장 없는 것이 바로 Stub이 아닐까 생각합니다.
만약 Stub 이 최소한의 기능을 가졌다면, 그것보다 좀 더 많은 기능(ex, 입력에 따라 다른 결과를 반환하는 것)이 필요하다면 Fake 을 고려할 수 있습니다.
Fake
Fake 는 앞서 설명한 것 처럼 Stub 보다는 조금 더 많은 기능을 가졌습니다.
Fake 객체는 진짜 객체의 행동을 흉내내면서, 진짜 객체를 사용할 때 생기는 부수 효과나 연쇄동작이 일어나지 않도록 경량화하고 최적화한 것입니다.
interface UserRepository {
void save(User user);
User findById(long id);
User findByUsername(String username);
}
위와 같은 인터페이스가 있다면, Stub 을 만들 수도 있지만 간단한 인메모리 데이터베이스를 만들어 사용할 수 있습니다.
class FakeUserRepository implements UserRepository {
private Collection<User> users = new ArrayList<User>();
public void save(User user){
...
}
... 중략
}
위와같이 사용하는데, 실제 객체를 사용하는 것보다 더 빠르게 동작할 뿐만 아니라, 테스트에서 DB의 의존성 또한 격리시키게 되었습니다.
Spy
테스트 스파이는 기밀을 훔친다. - p71 이펙티브유닛테스팅
왜 Spy 는 기밀을 훔칠까? Spy 는 Stub 이나, Fake 처럼 개발자가 임의로 만든 객체가 아닌 실제 객체를 사용합니다.
Spy 을 사용하는 이유는 단순합니다.
테스트 코드를 작성하다 보면 종종 void 을 반환하는 메서드를 어떻게 테스트해야 되지? 라는 생각을 하게 됩니다.
public <T> void filter(List<T> list, Predicate<T> predicate) {
list = list.stream().filter(predicate).collect(Collectors.toList());
}
// filter 가 잘 동작됨을 어떻게 확인할 수 있지?
이럴 때 사용할 수 있는 Spy 입니다. 마치 잠복근무를 하는 경찰이 목격한 내용을 보고하는 것과 비슷합니다.
@Test
void name2() {
List<String> strings = new SpyList<>();
strings.add("A");
strings.add("B");
removefilter(strings, (t) -> t.equals("A"));
assertThat(strings.has("A")).isFalse();
assertThat(strings.has("B")).isTrue();
}
Mocktio 에서는 @Spy 에 대한 구현이 있습니다.
테스트 스파이는 목격한 일을 기록해두었다가 나중에 테스트가 확인할 수 있게끔 만들어진 테스트더블입니다.
Mock
Spy 가 실제 객체를 활용해 기록했다가 테스트가 확인하는 용도라면, Mock 은 어떤 것이든 테스트가 원하는 것을 반환하도록 해주는 테스트 더블입니다.
특정 조건이 발생하면 미리 약속된 행동을 취합니다.
그렇다면 Test Double
이 전달해주는 가치에 대해서 이야기해봅시다.
테스트하고자 하는 객체의 의존성을 격리한다.
테스트의 속도를 더 빠르게!
예측 불가능한 실행 요소를 제거한다.
테스트하고자 하는 객체의 의존성을 격리한다.
격리를 한다는 것은 코드을 2가지로 분류할 수 있다는 이야기입니다.
- 테스트 대상 코드
- 테스트 대상 코드와 상호작용하는 코드
테스트 대상 코드를 상호작용하는 코드로부터 의존성을 분리하는 것을 의미합니다.
분리함으로써,
테스트는 초점이 분명해지고, 이해하기도 쉬워지며, 테스트 코드에 필요한 설정이 간편해집니다.
테스트의 속도를 더 빠르게!
위에서 언급한, Stub, Fake, Spy, Mock 등의 테스트 더블을 활용하게 된다면,
전에 계산해둔 경로를 반환하도록 하여 쓸데없이 기다리는 시간이 줄어들어 테스트 속도를 더 빠르게 개선할 수 있게 됩니다.
예측 불가능한 실행 요소를 제거한다.
테스트 더블을 사용해야되는 이유는 무엇을 테스트하고 싶은 것인지 명확히 할 수 있다.
예를 들어, 현재 시간에 의존적인 테스트가 있다면 어떻게 테스트 할 것인가? 그런 부분을 테스트 더블을 활용해 격리하여
테스트하고자 하는 곳에 집중할 수 있게 됩니다.
[연관 포스팅]
좋은-테스트에-대해서-한번-떠들어보자in-이펙티브유닛테스팅
[참고자료]
'가치관 쌓기 > 개발 돌아보기' 카테고리의 다른 글
왜 테스트 코드를 작성하는 걸까? (0) | 2022.10.29 |
---|---|
DB AutoIncrement 가 아니라, 왜 굳이 IDGenerator Server 을 만들었을까? (2) | 2022.10.05 |
좋은 테스트에 대해서 이야기 하기 (0) | 2022.09.18 |
엄청 많은 속성을 가진 객체를 테스트해야 한다면 어떻게 해야될까?(with. test Data Builder) (0) | 2022.09.09 |
적절한 마이크로서비스를 도입하는 시기는 언제일까? (8) | 2022.07.09 |
댓글