테스트 더블(test double)

테스트 작성시, 테스트 대상 코드와 상호작용하는 객체
 

테스트 더블의 역할

  • 테스트 대상 코드를 격리한다.
  • 테스트 속도를 개선한다.
  • 예측불가능한 실행 요소를 제거한다.
  • 특수한 상황을 시뮬레이션한다.
  • 감춰진 정보를 얻어낸다.

 
 

테스트 더블의 종류

1. Dummy
가장 기본적인 테스트 더블 유형.
구현이 포함되어 있지 않고, 주로 매개 변수 값으로만 필요하며 다른 곳에 이용되지 않는 경우 주로 사용된다.
진정한 의미의 dummy는 구현을 제외한 인터페이스 or 기본 클래스의 파생 개체.
 
dummy 객체는, 단지 인스턴스화된 객체가 필요할 뿐
해당 객체의 기능까지는 필요하지 않은 경우에 사용한다.
따라서, 해당 dummy 객체의 메소드가 호출됐을 때의 정상 동작은 보장되지 않는다.
보통은 타입 기본값(0, null, false 등)으로 반환값을 만들어주는 선에서 마무리한다.
 
예)

public class DummyCoupon implements ICoupon {
    @Override
    public boolean isAppliable(Item item) {
        throw  new UnsupportedOperationException('호출되지 않을 예정');
    }
}

 
 
2. Stub (토막, 꽁초, 남은 부분, 몽당연필 … )
테스트 stub은 dummy 객체가 마치 실제로 동작하는 것처럼 보이게 만들어놓은 객체다.
 
stub은 실제 코드나 아직 준비되지 못 한 코드의 행동으로 가장하는 메커니즘을 말한다.
stub은 호출자를 실제 구현물로부터 격리시킬 목적으로 런타임에 실제 코드 대신 삽입되는 코드 조각이다.
 
stub의 목적은 원래의 구현을 최대한 단순한 것으로 대체하는 것.
dummy보다 한 단계 발전한 형태로, 인터페이스 or 기본 클래스가 최소한으로 구현된 형태.
일반적으로 void를 반환하는 메서드에는 구현이 없고,
값을 반환하는 메서드는 하드 코딩된 값을 반환한다.
 
대부분의 경우 stub을 이용한 테스트의 신뢰도는 높은 편이다.
단점을 말하자면, stub 제작이 그리 녹록하지 않다는 것이다.
흉내내야 할 시스템이 복잡하다면 더욱 그러하다.
 
일반적으로, stub은 포괄적인 코드 블록을 대체하는데 더 적합하다.
즉 stub은 파일 시스템이나, 서버와의 커넥션, 데이터베이스 등
외부 시스템 전체를 대체하는데 주로 활용된다.
 
stub은 단순해야 하며, 절대 테스트나 유지보수가 요구되는
또 하나의 애플리케이션이 되어서는 안 된다는 것을 기억하라.
 
예)

public class StubCoupon implements ICoupon {
    @Override
    public boolean isAppliable(Item item) {
        return true; // 하드코딩 true 반환
    }
}

 
stub과 dummy의 차이점

  • 단지 인스턴스화 될 수 있는 객체 수준이면 dummy
  • 인스턴스화 된 객체가 특정 상태나 모습을 대표하면 stub

 
 
3. Fake
stub은 거의 하드코딩된 형태이기 때문에 로직이 들어가는 부분은 테스트할 수 없다.
이를테면, 특정 쿠폰이 구매 제품에 적용되는지 여부에 따라,
결제액이 바뀌는걸 테스트한다고 생각해보자.
 
조금 사기 같아 보일 수 있지만, 다음과 같이 고쳐보자.
 
예시)
public class StubCoupon implements ICoupon {
@Override
public boolean isAppliable(Item item) {
if(item.getCategory().equals(‘후라이팬’)) {
return true;
}else if(item.getCategory().equals(‘시계’)) {
return false
}
}
}
 
로직이긴 로직인데, ‘그때그때 달라요’식으로 하드코딩되어 있는 로직이다.
테스트 더블로 만들어진 객체가 이 정도까지 지원하는 수준으로 발전하면
단순 stub 수준은 벗어났다고 본다.
마치, 실제 로직이 구현된 것처럼 보이는데, 이렇게 만들어진 객체를 fake 객체라고 한다.
 
모양만으로는 stub과 fake의 경계도 딱 구분 짓기는 어렵다.
다만 stub은 하나의 인스턴스를 대표하는데 주로 쓰이고
fake는 여러 개의 인스턴스를 대표할 수 있는 경우이거나,
좀 더 복잡한 구현이 들어가 있는 객체를 지칭한다.
 
fake 객체는 복잡한 로직이나, 객체 내부에서 필요로 하는 다른 외부 객체들의 동작을
비교적 단순화하여 구현한 객체다.
 
 
4. Spy
테스트에서 특정 객체가 사용됐는지,
그리고 그 객체의 예상된 메소드가 정상적으로 호출됐는지를 확인해야 하는 상황이 발생한다.
 
보통은 호출 여부를 몰래 감시해서 기록했다가,
나중에 요청이 들어오면 해당 기록 정보를 전달해준다.
특정 메소드의 정상 호출 여부 확인을 목적으로 구현되며, dummy부터 fake 객체에 이르기까지
테스트 더블로 구현된 객체 전 범위에 걸쳐 해당 기능을 추가할 수 있다.
 
stub과 유사하지만 멤버를 호출하기 위한 인스턴스를 클라이언트에 제공한다는 점에서 차이가 있다.
예상대로 멤버가 호출되었는지 확인할 수 있도록 호출해야 할 멤버도 기록한다.
 
예)

public class SpyCoupon implements ICoupon {
    private int isAppliableCallCount;
    @Override
    public boolean isAppliable(Item item) {
        isAppliableCallCount++; // 호출되면 증가
        if(item.getCategory().equals('후라이팬')) {
            return true;
        }else if(item.getCategory().equals('시계')) {
            return false
        }
    }
    // spy 기능을 위해 임의 추가한 메소드.
    public int getIsAppliableCallCount() {
        return this.isAppliableCallCount; // isAppliable 함수가 몇번 호출되었는지를 반환
    }
}

 
 
일반적으로 테스트 spy는 아주 특수한 경우를 제외하고 잘 쓰이지 않는다.
보통은 테스트 spy가 필요한 경우에도 Mock 프레임워크를 이용하는 것이 더 편하기 때문이다.
 
 
– 5. Mock
테스트에 따른 분류

  • 상태 기반 테스트(state base test) :
    객체가 특정 시점에 자신만의 상태를 갖는 특징에 기반한 테스트 방식.
    객체.setName(‘보라돌이’) 메소드를 호출했으면, getName() 메소드로 확인해보는 식이다.
     
  • 행위 기반 테스트(behavior base test) :
    올바른 로직 수행에 대한 판단의 근거로, 특정한 동작의 수행 여부를 이용한다.
    보통은 메소드의 리턴값이 없거나(void 메소드), 리턴값을 확인하는 것만으로는 예상대로 동작했음을 보증하기 어려운 경우에 사용한다.

 
 
메소드A가 정상 동작했을 경우 메소드B가 반드시 호출되는 구성이라면,
반대로 메소드B의 호출 여부로 메소드A의 정상 여부를 판단할 수 있다고 보는 것이다.
따라서, 이럴 때는 메소드B의 호출 여부를 확인하는 것이 테스트 시나리오의 종료 조건이 된다.
 
초창기에 나온 Mock 프레임워크들은 태생 자체가
이런 행위 기반 테스트를 지원해주기 위해서인 경우가 대다수였다.
 
일상에서는 ‘테스트 더블’이란 단어와 ‘Mock 객체’라는 단어가
거의 동등한 의미로 사용되는 경우가 더 많았다.
 
stub, spy, fake는 구분하기가 다소 모호하다.
mock은 매우 복잡하기도 단순할 수도 있기 때문에
경우에 따라 dummy, stub, spy, fake 범위에 걸쳐 있다.

카테고리: etc

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.