Mock 테스트 (feat. Test Double)

bp.chys·2020년 5월 18일
1

Spring Framework

목록 보기
8/15

Mock과 Mockito

  • Mock 객체란 모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체를 의미한다.
  • Mockto는 객체를 Mocking하여 테스트할 수 있도록 지원하는 일종의 프레임워크. 스프링부트에서는 기본 스펙으로 사용한다.

언제 Mock Test를 할까?

  1. 테스트 작성을 위한 환경 구축이 어려울 때
    • 환경 구축을 위한 작업 시간이 많이 필요한 경우
    • 특정 모듈을 아직 갖고 있지 않아서 테스트 환경을 구축 하기 힘든 경우
    • 타 부서와의 협의나 정책이 필요한 경우
  2. 테스트가 특정 경우나 순간에 의존적일 때
  3. 테스트 시간이 오래 걸리는 경우

Test Double

  • 실제 객체를 대체하여 테스트에 사용할 수 있는 객체를 의미한다.
  • 대표적으로 Fakes, Stubs, Mocks로 분류할 수 있다.

Fakes

  • Fakes는 실제 동작하는 구현이 존재하지만, 실제 프로덕션 구현과 동일하지 않은 객체이다.
  • 일반적으로 Fakes는 실제 객체보다 훨씬 단순한 구조를 취하고 있고, 의존성도 간소화하여 구현한다.
  • 대표적인 예로는 Repository를 자바 컬렉션을 사용하여 가짜로 구현하는 것이다.
  • 이를 통해 데이터베이스를 연결하거나, 비용이 큰 요청을 직접 수행하지 않고도 단위 테스트를 수행할 수 있다.
public interface MemberRepository extends JpaRepository<Long, Member> {

    Optional<Member> findByName(String name);
}

public class FakeMemberRepository implements MemberRepository {
       
       List<Member> members = new ArrayList<>();
       
       public FakeAccountRepository() {
              this.members.add(new Member("bossdog", "bsdg@gmail.com");
              this.members.add(new Member("pobi", "javajigi@gmail.com");
       }
       
       @Override
       Member save(Member member) {
           members.add(member);
           return member;
       }
       
       @Override
       Optional<Member> findByName(String name) {
              return members.stream()
                      .filter(m -> m.isName(name))
                      .findFirst();
       }
}

Stubs

  • Stubbing이란 가짜 구현체를 사용하는 것이아니라 어떤 메서드를 실행했을 때 반환할 것으로 예상하는 상태를 미리 지정해놓는 행위를 의미한다. 미리 지정하지 않은 객체를 받으면 예외를 던질 수 있다.
  • Mockito를 사용한다면 Stubbing이 익숙한 방식이다.
  • 예를 들어 서비스 계층에서 회원을 저장하는 메서드를 TDD로 구현한다면 다음과 같이 가능할 것이다.
@ExtendWith(MockitoExtension.class)
public class MemberServiceTest {

    @Mock private MemberRepository; // 레파지토리를 Stubbing할 객체로 지정한다.
    
    @Test
    public void saveTest() {
        // given
        Member member = new Member("bossdog", "bsdg@gmail.com");  // 반환 예정 상태 미리 지정
        when(memberRepository.save(any())).thenReturn(member);  // stubbing!
        
        // when
        Member member = memberService.save(member);
        
        // then
        assertThat(member.getName()).isEqualTo("bossdog");
    }
}

Mocks

  • Mock Object는 실제 클래스를 본 떠서 만든 모의 객체라고 할 수 있다.
  • 즉 실제 클래스의 인터페이스를 사용하여 같은 동작을 할 수 있지만, 상태를 변화시키지는 않는다.
  • 아니 애초에 생성자를 통해 생성되는 객체가 아니므로 정상적인 상태를 갖지 못한다고 보는게 맞을 것이다.
  • 그럼 Mocks는 무슨 역할을 하는가? 바로 특정 메서드가 호출되었는지를 검사할 수 있다.
@Test
public void mockTest() {
    Member member = mock(Member.class);
    
    Team team = new Team(member);
    team.win();

    verify(member).increasePoint();
}
  • 위 예시 처럼 Mock 객체로 선언한 member가 자신이 속한 팀이 승리했을때 실제로 point가 증가했는지 확인할 수 있다. increasePoint()를 실제로 호출했는지 확인할 수 있는 것이다.
  • 이 역시 내부 로직을 몰라도 모의하여 결과나 의도한 프로세스를 도출해낼 수 있으므로 단위테스트를 작성할 때 유용하다고 할 수 있다.

결론

Test Double인 Fakes, Stubs, Mocks에 대해 간단히 그 차이를 살펴보았다. 결과적으로 말하면 세 가지 방식은 처음 문제였던 테스트 격리에 관해서 실제 데이터 베이스를 사용하지 않기 때문에 모두 테스트 격리에 도움이 되는 방법들이라고 할 수 있을 것이다.

어떤 방식이 가장 효과적이냐고 묻지는 말자.
앞서 살펴본 Test Double들은 존재하는 그 목적이 다르기 때문에 상황에 따라서 가장 적절하고 효과적인 방식을 선택해서 사용하는 것이 현명할 것이다.

웹 애플리케이션 아키텍처는 보통 계층구조로 되어있다. Test Double을 적절히 잘 사용한다면, 계층구조의 단점을 피해 TDD로 개발하는데에도 많은 도움이 될 것이다.
그러나 분명 Mock Test는 단점도 있다. 테스트를 통과했다고해서 실제 운영 조건에서도 정상적으로 기능이 동작하리라 확신할 수는 없다는 것이다. 이를 보완할 수 있도록 인수테스트나, 통합테스트를 추가로 작성하는 것이 좋다고 생각한다.


참고자료

Test Doubles — Fakes, Mocks and Stubs.TestDouble - martin fowlerUnit Testing: Fakes, Mocks and Stubs

profile
하루에 한걸음씩, 꾸준히

0개의 댓글