• Test Double
    DEV 2023. 12. 2. 19:33

    red & blue

    테스트 대역 = Test Double

    개발하다 보면 여러 객체들 간 의존성이 발생

    → 의존성이 단위 테스트를 어렵게 만듦

    → 진짜 객체는 느리고 자원을 많이 사용하기 때문에

    → 실제 객체를 대신할 수 있는 객체가 필요

    → 가짜객체 생성  ⇒ test double (테스트 대역)

    💡 test double을 mock이라고 말하는 경우도 있지만,
    mock은 좁은 의미로 test double의 한 종류, 넓게는 테스트 더블을 의미하기도 한다.

     

    대역(test double)의 종류

    • stub, fake, spy, mock
    • 역할을 명확히 나누는 것이 애매할 경우가 있고, 서로의 특성을 조금씩 포함

    input & output

    • 입력 → 논리 → 출력
    • 모든 프로그램은 입력 → 출력의 흐름으로 이어지는데, 입출력의 종류에는 직접입력, 집접출력, 간접입력, 간접출력이 있다.

    직접입력, 직접출력

    • 공개된 인터페이스를 통해 입출력
    • 인터페이스만 확인하면 어떤 입출력을 갖는지 알 수 있다
    • 다루기 쉬움

    간접입력, 간접출력

    • 입력된 인터페이스를 통한 입출력
    • 인터페이스가 모듈에 입력된 후 입력된 인터페이스를 통해서 데이터를 입력받거나 출력하는 경우
    • 상대적으로 복잡

    간접입력, 직접입력

    대역(test double)의 종류

    • stub = 간접입력
    • spy = 간접출력
    • fake = 실제 동작하는 구현을 제공, (mysql 대신 h2를 이용해 구현한 가짜 대역)
    • mock = 기대한 대로 상호작용하는지 행위를 검증, stub이자 spy도 된다.

    Mockito

    • mock 객체를 지원하는 테스트 프레임워크
      • stub, spy를 쉽게 처리 가능
    • 대부분의 mock 객체 프레임워크가 채택한 mocking 방식
      • proxy-based mocking → mockito
      • classloader remapping-based mocking → mockito

    Proxy pattern

    • 특정 객체로의 접근을 제어하는 대리인을 제공
    • 기존 코드에 변경을 가하지 않으면서 기능을 추가

    proxy pattern

    • client에서 proxy 객체를 호출하여 부가적인 코드를 수행한 뒤 참조변수를 통해 RealSubject를 호출
    • 클라이언트는 proxy를 통해 객체기능을 사용
      → 서비스객체의 SRP(단일책임원칙)을 해치지 않고 proxy를 통해 책임을 추가
      ⇒ 접근권한, 부가기능, 리턴값 변경, 로깅, 트랜잭션...

    in Mockito

    프록시 클래스 생성 → 프록시 클래스 인스턴스화 → mockHandler 인스턴스에 set

    Dynamic proxy

    • 런타임에 특정 인터페이스들을 구현하는 클래스나 인스턴스를 만드는 기술

    1. JDK Dynamic proxy

    2. CGLIB (Code Generator Library)

    • JDK Dynamic proxy 보다 상대적으로 빠름
    • 인터페이스가 없을 경우 사용가능
    • Spring Boot AOP

    Example

    mock

    • 테스트하려는 대상(System Under Test)에 의존이 있는 객체를 mocking 하여 처리
    @InjectMocks
    private SetupConfig setupConfig;
    @Mock
    private SetupServiceImpl setupService;
    @Mock
    private BatchProperties batchProperties;
    
    @Test
    void passBeforeTask() {
        //given
        given(setupService.findData(batchProperties.getId()))
                        .willReturn(data);
        given(setupService.indexData(any(Data.class)))
                .willReturn(Data);
    
    ....
    
    }

    stub

    • 테스트 하려는 대상의 의존이 있는 객체의 인터페이스를 상속한 클래스를 stub으로 만들어 임의 값을 리턴한다.
    private SetupConfiguration setupConfiguration;
    private StubSetupServiceImpl stubSetupService = new StubSetupServiceImpl();
    private BatchProperties batchProperties = new BatchProperties();
    
    @Test
    void passBeforeTask() {
        //given
        SetupConfig setupConfig = new SetupConfig(batchProperties, stubSetupService);
        stubSetupService.setMuffinCollection(muffinCollection);
    
        .....
    
    }
     

    상태검증

    //when
    
    setupConfig.beforeTask(taskExecution);
    
    //then
    
    final Batch batch = setupConfig.getBatch();
    
    assertThat(batch.getTask().getIsRunning())
    
            .isEqualTo(true);
    
    assertThat(batch.getStream().getIsRunning())
    
            .isEqualTo(false);
    • 객체의 상태를 살펴봄으로 상태를 검증

    행위검증

    //when
    
    setupConfig.beforeTask(taskExecution);
    
    //verify
    
    verify(setupService, times(1))
    
                    .findData(batchProperties.getId());
    
    verify(setupService, times(1))
    
            .indexData(any(MuffinCollection.class));
    • 특정메서드가 호출되었는지 등 행위를 검증

    Mockist vs Classicist

    • mock을 이용하면 대역클래스(stub, spy..)를 만들지 않아도 되니 편하다.
    • 하지만 출력을 어떻게 만들어 내는지를 검증하게 되고, (행위검증) → mock
    • SUT(System Under Test:테스트하려는 대상)가 결과를 만들어 낼 때 의존성과 어떻게 의사소통을 하면서 결과를 만드는지 확인하게 되면 정보숨김(information hiding)에 위배되는 경우가 발생하기도 한다.
    • 정보숨김: 어려운 설계, 변경가능한 설계 결정을 모듈 안쪽에 숨기는 기법

     

    test code는 SUT의 interface를 통해 테스트

    💡 mock을 사용 → SUT 내부동작을 검증 → 내부구현의 지식을 갖게 됨
    → 지식이 테스트코드로 유출 → 테스트 코드가 구현에 의존
    → 리펙터링이 힘들어진다. → 테스트가 쉽게 깨질 수 있다.

    728x90

    'DEV' 카테고리의 다른 글

    Spring batch integration test (feat.Elasticsearch)  (0) 2023.12.02
    K8S, DNS 간헐적 5~15초 지연  (0) 2023.12.02
    Dependency Mechanism  (1) 2023.12.02
    slf4j, facade, TDD  (1) 2023.12.02
    Elasticsearch Shard & 성능  (1) 2023.12.02
go.