Test

Fake Double로 대체해서 테스트하는 방법

ch-yang 2023. 3. 17. 17:47

※ 작성 당시에는 좋은 방법이라고 생각했는데, 지금은 통합 테스트를 하는데 Repository를 Fake로 치환하는 것은 좋지 않다고 생각합니다. 그래도 dev 환경과 product 환경에서 다른 클래스를 사용해야 한다면 쓸만한 방법인 것 같아 글은 지우지 않았습니다.

테스트 코드 링크

@SpringBootTest를 사용해서 통합 테스트를 진행 중이다. 그런데 테스트에서 TargetReository 인터페이스를 구현하는 RealRepository가 무겁고 세팅도 번거롭다. 그래서 가벼운 FakeRepository로 대체해서 테스트를 진행하고자 한다.

여기서 RealRepository는 @Component를 붙여서 자동으로 Bean 등록된다.

TargetRepository 인터페이스

public interface TargetRepository {
    void save(String key, String value);

    String get(String key);

    void delete(String key);
}

RealRepository 클래스

@Component
public class RealRepository implements TargetRepository {

    Map<String, String> map = new HashMap<>();

    @Override
    public void save(String key, String value) {
        System.out.println("RealRepository.save");
        map.put(key, value);
    }

    @Override
    public String get(String key) {
        System.out.println("RealRepository.get");
        return map.get(key);
    }

    @Override
    public void delete(String key) {
        System.out.println("RealRepository.delete");
        map.remove(key);
    }
}

먼저 RealRepository를 사용해서 테스트를 진행했다.

@SpringBootTest
class RealRepositoryTest {

    @Autowired
    TargetRepository targetRepository;

    @Test
    public void realTest() {
        String key = "key";
        String value = "value";

        targetRepository.save(key, value);
        assertThat(targetRepository.get(key)).isEqualTo(value);
        targetRepository.delete(key);
        assertThat(targetRepository).isInstanceOf(RealRepository.class);
    }
}

/* 출력
RealRepository.save
RealRepository.get
RealRepository.delete
*/

이제 RealRepository를 대체하는 FakeRepository로 바꿔서 테스트를 진행해 보자.

FakeRepository 클래스는 Test 폴더에 구현했고 자동 빈 등록은 하지 않았다. 그리고 @Primary를 붙여서 DI 우선순위를 높였다.

FakeRepository 클래스

@Primary
public class FakeRepository implements TargetRepository {

    Map<String, String> map = new HashMap<>();

    @Override
    public void save(String key, String value) {
        System.out.println("FakeRepository.save");
        map.put(key, value);
    }

    @Override
    public String get(String key) {
        System.out.println("FakeRepository.get");
        return map.get(key);
    }

    @Override
    public void delete(String key) {
        System.out.println("FakeRepository.delete");
        map.remove(key);
    }
}

이제 대체된 FakeRepository로 테스트를 진행해 보자.

@SpringBootTest
@Import(FakeRepository.class)
public class FakeRepositoryTest {

    @Autowired
    TargetRepository targetRepository;

    @Test
    public void fakeTest() {
        String key = "key";
        String value = "value";

        targetRepository.save(key, value);
        assertThat(targetRepository.get(key)).isEqualTo(value);
        targetRepository.delete(key);
        assertThat(targetRepository).isInstanceOf(FakeRepository.class);
    }
}

/* 출력
FakeRepository.save
FakeRepository.get
FakeRepository.delete
*/

@Import로 FakeRepository도 Bean으로 등록을 했다. 그러면 targetRepository에 주입되는 Bean 대상은 RealRepository와 FakeRepository가 되는데, @Primary가 붙은 FakeRepository가 의존성 주입 우선순위가 높아서 FakeRepository가 주입된다.

@Profile, @ActiveProfile을 사용해도 이와 비슷하게 테스트가 가능하지만, @Primary를 사용한 방법이 RealRepository 코드를 @Profile를 붙여서 수정할 필요도 없이 깔끔하고 간단했다.

대신 @Profile을 사용한 방법과 달리 RealRepository와 FakeRepository가 둘 다 Bean으로 등록되니 TargetRepository로 추상화해서 작성한 코드가 아니면 RealRepository가 그대로 주입되는 건 주의해야겠다.

'Test' 카테고리의 다른 글

단위 테스트 - 마틴 파울러  (0) 2023.02.15
Test 용어 정리  (0) 2023.02.14
테스트 관련 사이트  (0) 2023.02.14