테스트 더블을 활용한 단위 테스트 (feat. Mock)

YEON·2022년 6월 11일
0

추가적으로 조금 더 자세히 정리할 예정입니다.

단위 테스트를 작성하기 위하여 Mock 객체에 대하여 학습을 진행하고 포스팅한 글입니다.


단위 테스트

단위 테스트는 특정 단위(테스트 대상)가 의도한대로 작동하는지 검증하는 것이다.
단위 테스트 작성 시 관계를 맺고있는 대상 (협력 객체)이 있는 경우를 고려해서 테스트를 작성해야 한다.
이때, 협력 객체를 실제 객체로 사용하는지 Mock(가짜) 객체 로 사용하는지에 따라 테스트 구현이 달라진다.


테스트 대상이 협력 객체 를 가질 때

  • 실제 객체 를 사용 하면 협력 객체의 행위를 협력 객체 스스로가 정의 한다.
    그 얘기는 즉슨 테스트 대상은 협력 객체의 상세 구현을 알 필요가 없다. 하지만 협력 객체의 동작 여부에는 영향을 받을 수 밖에 없다.
  • 가짜 객체 를 사용 하면 협력 객체의 행위를 테스트가 정의 한다.
    때문에 의존하는 객체와 상관없이 온전히 그 테스트 대상만 검증할 수 있다. 하지만 테스트가 협력 객체의 상세 구현을 알아야한다.

그렇다면 언제 실제 객체 로 구현하고 가짜 객체 로 구현할까?

  • 실제 객체를 사용할 것인지, 가짜 객체를 사용할 것인지는 테스트하는 단위가 협력 객체들과 통합되어야 하는지 고립(분리)되어야 하는지 통합과 고립(Sociable and Solitary) 을 고려해야한다.
  • 테스트 코드를 작성할 때, 가짜 객체를 활용하면 실제 객체를 사용할 때보다 조금 더 편하게 테스트를 작성할 수 있지만 상세 구현에 의존하는 테스트가 될 수 있다.

그래서 협력 객체를 실제 객체 로 테스트를 구현할 때와 , Mock 객체 를 사용하여 테스트를 구현하는 경우를 비교하고
Mock 객체에 관해 학습해보기 위해 포스팅을 진행하였다.


단일 단위 테스트 vs 협력 객체를 사용하는 단위 테스트
위의 테스트는 Station 이란 객체에 관하여만 검증을 했고, 아래의 테스트에서 Line 은 Station 에 대한 의존을 가지고 있는 것을 확인할 수 있다.

@Test
void updateName(){
	Station 강남역 = new Station("강남역");
    강남역.updateName("교대역");
    assertThat(강남역.getName()).isEqualTo("교대역");
}
@Test
void addSection(){
	Station 강남역 = new Station("강남역");
    Station 교대역 = new Station("교대역");
    Station 교대역 = new Station("역삼역");
    Line line = new Line("2호선", "green", 강남역, 교대역, 10);
    line.addSection(교대역, 역삼역, 5);
    assertThat(line.getStations())
    		.contaionsExactlyElementsOf(Arrays.asList(강남역,교대역,역삼역));
}



Test Double

Test Double이란?
위에서 설명한 가짜 객체 라고 볼 수 있다.
테스트 목적으로 실제 객체 대신 사용되는 모든 종류의 척도 객체 를 의미한다.
즉, 실제 (예 : 클래스, 모듈 또는 함수)를 가짜 버전으로 대체한다는 의미이다.
가짜 버전은 실제와 같은 것처럼 보이고 (동일한 메소드 호출에 대한 답변) 단위 테스트 시작시 스스로 정의한 미리 준비된 답변으로 답변한다. Test Double에는 다 똑같은 Test Double 이 아니라 가짜 객체를 활용하는 방식(종류)이 여러가지가 있지만 '대체 ' 한다는 큰 의미에서는 같다고 할 수 있다.
ex) fake 방식으로 처리한 test double




Stubbing

Stubbing 방식은 테스트 동안 호출이 되면 미리 지정된 답변으로 응답하는 것이다.
미리 프로그램된 것 외의 것에 대해서는 응답하지 않는다.

Test Double 을 사용 할 경우 테스트에서 협력 객체의 세부 구현을 알아야 한다.
때문에 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.findAllLines();
        // then
        assertThat(responses).hasSize(1);
    }
}
  • Spring 를 활용할 경우 다음과 같이 테스트를 작성할 수 있다.
@DisplayName("단위 테스트 - SpringExtension을 활용한 가짜 협력 객체 사용")
@ExtendWith(SpringExtension.class)
public class SpringExtensionTest {
    @MockBean
    private LineRepository lineRepository;
    @MockBean
    private StationRepository stationRepository;
    @Test
    void findAllLines() {
        // given
        when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
        LineService lineService = new LineService(lineRepository, stationRepository);
        // when
        List<LineResponse> responses = lineService.findAllLines();
        // then
        assertThat(responses).hasSize(1);
    }
}

+)
Mockist의 단위 테스트는 테스트 대상을 협력 객체로 부터 격리하기 위해 테스트 대상이 의존하는 모든 것을 가짜 객체로 대체한다는 테스트 방식이다.
한 마디로, Line과 Station 모두 잘 동작하는지 검증한다는 것은 단위와 단위의 통합이 잘 동작하는지를 검증하는 것이라고 볼 수 있다.




Mokito 를 이용한 테스트

Mockito는 Mock Object를 creation, verification, stubbing 해주는 java 라이브러리이다.
위와 관련하여 이전에 WIL 에 정리한 내용이 있다.
Mokito 를 이용한 테스트에 관하여 위의 포스팅 을 참조하면 좋을 것 같다.




주의사항

하지만, 무분별한 테스트 더블을 활용한 단위 테스트 는 주의해야한다.

테스트 더블을 통한 단위 테스트는 각 Layer, Componenet 간 연동이 되어 잘 되는 것까지는 보장하지는 못할 수 있다. (ex. 연동되는 모듈들의 버그 유무 등)

또한, Mock, Stub 를 사용하면 그만큼 테스트가 구현부를 상세하게 의존하기 때문에 테스트 더블 객체들은 깨지기 쉬운 테스트 케이스가 되기 쉽다.







[참조]
우아한테크캠프 Pro 3기 - 5주차 인수테스트 기반 TDD
https://jojoldu.tistory.com/637

profile
- 👩🏻‍💻

0개의 댓글