테스트 더블

푸드테크·2022년 7월 4일
1

테스트 더블

자동화된 테스트는 여러 외부 의존성이 주입되게 되는 순간부터 테스트하기는 굉장히 어려워지게 됩니다.

예를 들면, 어떠한 하나의 Repository를 테스트를 한다고 하면, 자연스레 데이터베이스의 의존성이 생겨버립니다.

우리의 테스트는 항상 성공해야 한다. 라는 조건에 외부 의존성이 끼면서,

우리의 테스트는 DB가 정상이어야 모든 테스트가 항상 성공한다. 로 변해버리게 됩니다.

이럴 때, 실제 동작하는 객체를 대체하여 만들어주고 우리가 원하는 대로 실행되게 하는 기법이 바로 테스트
더블
입니다.

테스트 더블에는 크게 Dummy, Fake, Stub, Spy, Mock이라는 형태가 존재합니다.

이번 포스팅에서는 이 테스트 더블 객체들에 대해 정리해보려고 합니다.

Dummy

가장 기본적인 테스트 더블이고, 뜻 그대로 아무런 것도 하지 않는 빈 껍데기 객체입니다.

코드로 보면 다음과 같습니다.

public interface Logger {
    public void info();
}

public class DummyLogger implements Logger {
    @Override
    public void info() {
        //아무것도 해주지 않는 상태로 유지합니다.
    }
}

info 라는 메소드를 구현해주어야 할 때,

info는 어떤 로그를 출력하는 기능을 담당한다고 하면, 테스트에서는 이 출력하는 기능을 테스트할 필요는 없습니다.

따라서 이러한 빈 껍데기를 이용해 테스트를 진행해줍니다.

동작하지 않아도 테스트에 영향이 미치지 않을 때, Dummy를 사용합니다.

Fake

말로 설명하는 것보다 바로 코드로 먼저 예시를 구현하겠습니다.

public interface Read {

    public Member findOneMember(Long id);
}

public class MemberRead implements Read {

    private final Map<Long, Member> map = new HashMap<>();

    @Override
    public Member findOneMember(Long id) {
        return map.get(id);
    }
}

편하게 JPA로 구현했었다고 가정하겠습니다.

원래의 로직이라면 Read인터페이스를 구현한 MemberJpaRead에선 JPA를 통해 DB로부터 값을 가져왔었지만,

그런 DB의존성을 배제하고 테스트를 진행해주기 위해서 구현을 Map으로 따로 진행해서 테스트 구현을 진행하고,

실제 제품 코드에는 적합하지 않은 코드입니다.

동작은 정상적으로 하지만, 실사용 객체처럼 완전하게 동작하지는 않습니다.

Stub

의도한대로 원하는 데이터만 나오게 만드는 테스트 더블입니다.

저희가 사용하고 있는 Mockito 라이브러리가 여기에 해당할 수 있습니다.

위의 Read 구현체를 다시한번 가져와보겠습니다.

public class MemberRead implements Read {

    @Override
    public Member findOneMember(Long id) {
        return new Member(1L, "testMember", "name", "test@email.com");
    }
}

구현 메소드는 같지만, 이번에는 testMember를 하나 만들어서 바로 돌려주는 메소드를 구현했습니다.

Member를 test Member만 의도대로 받겠다고 가정한다면 이런 Stub구현을 통해 테스트하면 좋을 것입니다.

단점은, 만약 testMember가 아니라면 이 메소드도 다르게 계속 변경을 해주어야 한다는 단점이 존재합니다.

Stub은 의도한 대로 인스턴스를 반환하게 할 때 쓰는 방법입니다.

Spy

실제 객체처럼 동작시킬 수도 있고, 필요한 부분에 대해서는 Stub로 만들어서 동작을 지정할 수도 있습니다.

어떠한 구현에 대해 이 메소드가 실제로 수행되었는지 여부를 판단하는 기법으로 보면 되겠습니다.

외부 의존성이 아니라 자바 코드로만 짜여진 로직을 동작하지 않게 하고 싶을 때 이 스파이를 사용해서

지나치게 만드는 방법도 존재합니다.

그래서 지정한 기능들만 Mocking해주고, 나머지는 그대로 사용하는 기법이 바로 이 스파이 테스트 더블입니다.

Mock

Mock은 위에서 설명했던 Stub을 이용하여 행위를 검증하는 기법입니다.

@ExtendWith(MockitoExtension.class)
class MemberServiceTest {
    @Mock
    private MemberRepository memberRepository;
    
    @InjectMocks
    private MemberService memberService;
    
    @Test
    void findMemberTest() {
        // 테스트 멤버가 나오게끔 구현
        BDDMockito.when(memberRepository.findById(1L)).thenReturn(new Member(1L, "testMember", "testName", "test@email.com"));
        
        Member actual = memberService.findOneMember(1L);
        Assertions.assertThat(actual.getName()).isEqualTo("testName");
    }
}

일단 Repository가 원하는대로 testMember가 나오게끔 만들어두고,

Service의 메소드를 의도한대로 동작시켜 검증을 수행해주었습니다.

이로써 Service의 메소드가 무엇을 반환하는지 전달할 수 있게 되었습니다.

정리

테스트 더블에 대해 생소하고 그리고 어떻게 써야할지 애매했던 테스트 기법들이 이제 조금 정리 되는 것 같습니다.

앞으로 이 테스트 더블을 토대로 좀 더 정교하게 테스트를 진행할 수 있을 것으로 보입니다.

더 나아가서는 이러한 테스트들을 작성하면서 의존을 떼낼 수 있는 리팩토링도 할 수 있을것 처럼 보입니다.

profile
푸드 테크 기술 블로그

2개의 댓글

comment-user-thumbnail
2022년 7월 5일

테스트 더블을 잘 활용하면, 외부 라이브러리에 의존하고 있는 부분들도 분리해 볼 수 있을 것 같다는 생각이드네요
좋은 글 잘봤습니다!

1개의 답글