목차

 

대역 필요성

"자동이체 기능"을 테스트하기 위한 외부 서비스에서 제공되는 정보를 이용하여 구현 시 문제가 되는 상황들

  • 해당 업체에서 상황별로 테스트 할 수 있는 카드번호 받아야함
  • 외부서비스에서 실수로 유효한 카드번호 삭제
  • 발급 받은 유요한 카드번호의 유효기간 지남

외부 서비스에서 제공하는 HTTP URL을 이용하여 제공되는 정보 구현이 어려움

 

외부요인이 테스트에 관여하는 주요 예시

  • 테스트 대상에서 파일 시스템을 이용
  • 테스트 대상에서 DB로 부터 데이터를 조회하거나 데이터를 추가
  • 테스트 대상에서 외부 HTTP 서버와 통신

TDD 는 "테스트 작성 -> 통과시킬 만큼 구형 -> 리팩토링" 의 과정을 짧은 흐름으로 반복해야 한다.

하지만, 테스트 대상이 이런 외부 요인에 의존하면 테스트를 작성하고 실행하기 어려워지며 실행 결과를 예측할 수 없게 만든다.

 

→ 이럴 때 외부 요인을 대신하는 대역을 이용하여 테스트를 작성한다.

 

대역을 이용한 테스트

대역을 이용하여  "자동이체 기능" 을 테스트하는 코드 작성해보기

 

CardNumberValidation을 대신한 대역 클래스

public class StubCardNumberValidator extends CardNumberValidator {
    private String invalidNo;
    private String theftNo;

    public void setInvalidNo(String invalidNo) {
        this.invalidNo = invalidNo;
    }

    public void setTheftNo(String theftNo) {
        this.theftNo = theftNo;
    }

    @Override
    public CardValidity validate(String cardNumber) {
        if (invalidNo != null && invalidNo.equals(cardNumber)) {
            return CardValidity.INVALID;
        }
        if (theftNo != null && theftNo.equals(cardNumber)) {
            return CardValidity.THEFT;
        }
        return CardValidity.VALID;
    }
}

- StubCardNumberValidation 에서 실제 카드번호 검증 기능을 구현하지 않고, VALID, INVALID 를 리턴하는 단순한 구현으로 대체

 

테스트 코드에 적용

....
@BeforeEach
void setUp() {
    stubValidator = new StubCardNumberValidator();
    stubRepository = new StubAutoDebitInfoRepository();
    register = new AutoDebitRegister(stubValidator, stubRepository);
}

@Test
void invalidCard() {
    stubValidator.setInvalidNo("111122223333");

    AutoDebitReq req = new AutoDebitReq("user1", "111122223333");
    RegisterResult result = this.register.register(req);

    assertEquals(INVALID, result.getValidity());
}

 

이와 같이 대역을 이용하면 필요한 외부 서비스 api 를 흉내내서 테스트에 적용할 수도 있다.

또한, DB 없이 DB 역할을 하는 클래스를 간단한게 구현하여 적용할 수도 있다.

 

대역의 종류

대역 종류  설명
스텁(Stub) 구현을 단순한 것으로 대체한다. 테스트를 위해 필요한 값만 제공하면 된다.
가짜(Fake) 제품에는 적합하지 않지만, 실제 동작하는 구현을 제공한다. DB 대신에 메모리를 이용해서 구현하는 방식이 이에 해당한다.
스파이(Spy) 호출된 내역을 기록한다. 기록한 내용은 테스트 결과를 검증할 때 사용한다.
모의(Mock) 기대한 대로 상호작용하는지 검증한다. 기대한 대로 동작하지 않으면 익셉션을 발생할 수 있다. 모듸 객체는 스텁이자 스파이도 된다.

 

❏ 약한 암호 확인 기능에 스텁 사용 ➡︎ 테스트를 위해 필요한 값 (암호가 약하다고 응답)만 제공

  • 실제 동작을 구현할 필요 x → 단순하게 약한 암호인지만 알려주면 된다.(스텁)
    • 스텁으로 하여금 암호 확인 요청이 오면 암호가 약하다고 응답하라고 설정
    • 실제 테스트 하는 클래스에 스텁용 클래스를 주입하여 결과를 리턴

      

❏ 리포지토리를 가짜 구현으로 사용 ➡︎ 리포지토리의 기본 기능을 구현

  • 동일한 ID를 가진 회원이 존재할 경우 익셉셥을 발생하는 테스트 → 가짜로 회원을 추가
    • User 클래스를 생성 - 엔티티 구현
    • 가짜 DB 로 MemoryUserRepository 클래스 구현
    • UserRepository 를 구현해서 가짜 DB에 메서드 추가

         Note : 상수를 이용하여 테스트를 통과시킨 다음에 구현을 일반화할 방법이 떠오르지 않으면
                     예를 추가하면서 점진적으로 구현을 완성해 나가면 된다.

 

❏ 이메일 발송 여부를 확인하기 위해 스파이를 사용

 

회원 가입에 성공하면 회원 가입 안내 메일 발송 여부를 확인하는 방법

  • 회원 가입 후 EmailNotifier "메일 발송 기능" 을 실행할 때 "가입한 이메일 주소" 를 사용했는지 확인 

EmailNotifier 의 스파이 대역(class SpyEmailNotifier) 구현하여 이메일 발송여부를 확인

  • 이메일 발송 기능을 호출 → SpyEmailNotifier 에서 호출 됨를 저장하여 True 로 반환
  • 이메일 발송 기능을 호출 → SpyEmailNotifier 에서 가입시 사용된 이메일을 저장하여 해당 이메일을 반환 
assertTrue(spyEmailNotifier.isCalled());
assertEquals("email@email.com", spyEmailNotifier.getEmail());

 

모의 객체로 스텁과 스파이 대체

- 주요기능 : 대역 객체(스텁, 스파이) 가 기대하는대로 상호작용했는지 확인하는 것

모의 객체를 위한 도구를 이용하면, 모의 객체가 기대하는대로 상호작용했는지 검증이 가능하다.

 

상황과 결과 확인을 위한 협업 대상(의존) 도출과 대역 사용

외부 API 를 직접 연동하여 테스트 코드에서 상황을 구현하거나 결과를 확인하기 어렵다.

이렇게 제어하기 힘든 외부상황이 존재하면 아래의 방법으로 의존을 도출하고 이를 대역으로 대신 할 수 있다.

  • 제어하기 힘든 외부 상황을 별도 타입으로 분리
  • 테스트 코드는 별도로 분리한 타입의 대역을 생성
  • 생성한 대역을 테스트 대상의 생성자 등을 이용해서 전달
  • 대역을 이용해서 상황 구성

"자동 이체 정보 등록 기능" 에서 외부 상황 분리

  • 카드번호가 유효한지 검사 하는 기능을 별도 타입으로 분리하고 대역을 생성
    • 별도 타입 : 카드번호 검사 기능 → 대역을 이용하여 구현 및 생성

회원 가입에 성공한 경우 이메일을 발송하는 기능

  • 회원 가입 기능 실행 이후에 이메일 발송 여부를 확인할 수단 필요
    • 이메일 발송 자체를 UserResiter 에서 구현하면 발송 여부를 확인하기 어려움
    • 결과 확인과 관련된 기능을 별도 타입으로 분리 → 대역으로 대체

당장 구현하기 어렵거나 오래걸리는 로직도 분리하기에 좋은 후보다.

대역과 개발 속도

TDD 과정에서 대역을 사용하지 않고 실제 구현을 사용한다면 발생하는 상황들

  • 카드 정보 제공 업체에서 도난 카드번호를 받을 때까지 테스트를 기다린다.
  • 카드 정보 제공 API가 비정상 응답을 주는 상황을 테스트하기 위해 업체의 변경 대응을 기다린다.
  • 회원 가입 테스트를 한 뒤에 편지가 도착할 때까지 메일함을 확인한다.
  • 약한 암호 검사 기능을 개발할 때까지 회원 가입 테스트를 대기한다.

모두 대기 시간이 발생 ➔ 대역을 사용하면 실제 구현이 없어도 실행 결과를 확인 가능

 

대역은 의존하는 대상을 구현하지 않아도 테스트 대상을 완성할 수 있게 만들어주며
이는 대기시간을 줄여주어 개발 속도를 올리는데 도움이 된다.

 

모의 객체를 과하게 사용하지 않기

 

모의 객체 사용의 편리성

  • 대역 클래스를 직접 만들 필요 없이 테스트 가능.

문제점

  • 결과 검증 코드가 길고 복잡해짐.
  • 여러 모의 객체 사용 시 코드 복잡도 증가.
  • 메서드 호출 검증 위주로 상호 작용이 조금만 바뀌어도 테스트가 깨질 위험.

주의사항

  • 모의 객체의 메서드 호출을 결과 검증의 주 수단으로 사용하지 말 것.

 

대안

  • DAO나 리포지토리 등 저장소에 대한 대역으로 메모리 기반의 가짜 구현 사용 권장.
  • 테스트 코드 관리가 용이해짐.

실용적 접근법

  • 모의 객체는 필요한 곳에 최소한으로 사용
  • 단위 테스트에서 모의 객체를 과도하게 사용하는 것보다, 통합 테스트를 통해 실제 상호작용을 검증하는 것이 좋음

 

 

 

 

 

+ Recent posts