단위테스트는 무엇일까? 라는 고민거리가 있었다.
어떤 객체에 대해 단위테스트를 작성하려는데 협력객체가 존재한다면 이는 단위테스트라고 할 수 있을까???
브라운이 설명해준 테스트에 대한 고찰 내용을 정리해보자.
단위테스트는 다음과 같은 특징이 있다.
다음과 같은 상황을 생각해보자.
Line line = new Line(new Station(), new Station);
Line이라는 객체를 테스트하기 위해 Line과 협력하고 있는 객체인 Station이라는 객체도 함께 사용해야한다.
협력객체가 같이 존재하는데 이걸 정말 단위테스트라고 할 수 있을까?
만약 Station에 대한 변경이 있으면 Line에 대한 테스트도 변경되고, 위에서 이야기한 작은 코드조각이라는 정의가 조금 빗나간다는 느낌이 들기도 한다.
그렇다면 협력하는 객체에 대해서는 단위테스트를 어떻게 수행할 수 있을까?
다음과 같은 두 가지 방법을 생각해 볼 수 있다.
통합 단위테스트 : 실제 객체를 사용하면서 구성한다.
@BeforeEach
setUp() {
Line line = new Line(new Station(), new Station);
}
고립 단위테스트 : Mock과 같은 가짜객체를 만들어서 조건에 맞춰서 기능이 동작하도록 구성한다.
@BeforeEach
setUp() {
Line line = mock(Line.class);
}
mock()
이라는 코드를 보자.
우리는 가짜 객체를 만들어서 테스트를 수행할 수 있다.
이 때 가짜 객체를 구현하는 방법은 여러가지가 있다.
Mock, Stub, Fake , Dummy 등등..
stub : (나무의) 그루터기(stump); (넘어진 나무 등의) 뿌리, 잘린 나머지
Stub은 어떤 기능을 대리하여 수행하는 코드라고 생각하면 된다.
우리는 협력 객체를 Stub으로 둘 수 있다.
실제로는 어떤 동작이 일어나는지 모르지만 어떤 동작을 했을 때 해당 결과를 임의로 정의할 수 있다.
대표적인 방식으로 아래와 같이 Mockito를 사용하는 방식을 확인할 수 있다.
@DisplayName("단위 테스트 - mockito를 활용한 가짜 협력 객체 사용")
public class MockitoTest {
@Test
void findAllLines() {
// given
LineRepository lineRepository = mock(LineRepository.class);
StationRepository stationRepository = mock(StationRepository.class);
when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
LineService lineService = new LineService(lineRepository, stationRepository);
// when
List<LineResponse> responses = lineService.findAl1Lines();
// then
assertThat(responses).hasSize(1);
}
}
Fake 객체는 실제로 사용된 객체는 아니지만 같은 동작을 하도록 구현된 객체다.
전략패턴이 대표적인 예시다.
자동차 경주코드를 예시로 가져와보자.
private static class AlwaysMoveGenerator implements RandomNumberGenerator {
@Override
public int generate() {
return 4;
}
}
RandomNumberGenerator라는 객체는 무작위 번호를 생성하는 역할을 한다.
우리는 특정 동작을 수행하도록 임의의 Fake 객체를 만들 수 있다.
객체가 필요하지만 내부 기능은 필요하지 않을 때 사용할 수 있는 방식이다.
Spy방식은 Stub의 역할과 동일하지만 정보를 더 기록한다.
예를 들면 어떤 Stub이 몇 번 호출 되었는지 기록하는 과정이 추가된다면 Spy 객체라고 볼 수 있겠다.
협력 객체가 존재할 때 테스트코드를 작성한다면 다음과 같은 고민을 할 수 있다.
Mockist TDD : 내부 구현이 되어있지 않은 상태에서 TDD를 하기 때문에 Out-In 방향으로 행위를 기반으로
TDD를 진행하는 방식이다.
Classical TDD : 실제 객체를 사용하여 TDD를 진행하기 때문에 최하위 도메인부터
구현하는 In - Out 방식을 사용한다.
Classical TDD는 다음과 같은 특징이 있다.
반대로 Mockist TDD는 다음과 같은 특징이 있다.
아는것에서 모르는 것으로 known to unknonw 방향을 잡는것이 좋다.
도메인 이해도가 높다면 Classical TDD가 조금 더 적합해보이고
그렇지 않다면 요구사항 및 기능/행위를 기반으로 Out-In 방식을 적용하면서 Mockist TDD를 사용해볼 만 하다.
추천 도서 : 단위테스트
추천 사이트 : 마틴파울러 블로그
https://codinghack.tistory.com/92
https://martinfowler.com/bliki/UnitTest.html
브라운 🌸🐻🌸