[xUnit 테스트 패턴] 11장 - 테스트 대역 사용


11장 - 테스트 대역 사용

간접 입력과 간접 출력

  • 의존 컴포넌트의 리턴 값이나 예외를 실제로 발생시키기 어려울 때 테스트 대역으로 해결 할 수 있다.

간접 입력을 신경 써야 하는 이유

  • DOC를 호출 후 리턴 값이나 예외를 처리하기 위한 목적으로 마련 됨
    • 이런 경로들을 테스트하지 않고 둔다면 테스트 안 된 코드가 된다.
  • DOC에서 발생시키고자 하는 예외가 간접 입력 테스트 조건의 좋은 예

간접 출력을 신경 써야 하는 이유

  • 뒷문 테스트 해야 하는 경우
    • 메시지 로깅
  • 간접 출력 테스트 조건
    • 메시지가 로그로 남게 되는 환경
  • 일부 간접 출력은 메소드 호출이나 메시지 형태로 다른 컴포넌트에 전달 된다.

간접 입력은 어떻게 제어할 수 있을까

  • 자신이 원하는 모든 유형의 리턴 값을 DOC가 리턴하게 제어할 수 있어야 한다.
  • 제어 위치를 통해 발생시키고 싶은 간접 입력의 유형
    • 메소드/함수의 러턴 값
    • 변경 가능한 인자 값(pass-by-reference로 전달한 함수 인자)
    • 예상되는 예외
  • 테스트스텁으로 함수가 원하는 값을 리턴하게 설정한 후 이를 SUT에 설치
    • SUT가 실행되는 동안 테스트 스텁은 함수 호출을 받아 이전에 설정된 값을 리턴한다.

간접 출력은 어떻게 검증할 것인가

  • 간접 출력을 테스트하려면 SUT가 호출하는 DOC의 API를 관찰할 수 있어야 한다.
    • 리턴 값도 제어할 수 있어야 한다.
  • 간접 출력 검증 두 가지 방식
    • 절차형 동작 검증
      • SUT가 실행되는 동안 특정 DOC로의 호출을 잡아내 SUT 실행이 끝난 후 원하는 대로 호출됐는지를 검사
    • 기대 동작 검증
      • 테스트의 픽스처 설치 단계에서 동작 명세를 만든 후 이를 실제 동작과 비교

대역으로 테스트하기

  • 테스트 대역은 협조적이고 원하는 대로 테스트를 작성할 수 있게 해주는 객체의 일종이다.

테스트 대역의 종류

  • 테스트 대역(Test Double)
    • 테스트의 실행 목적을 나타내기 위해 실제 컴포넌트 대신 설치하는 객체나 컴포넌트
  • 테스트 대역의 동작 방식 4가지
    • 더미 객체(Dummy Object)
      • SUT 인자로 넘겨주긴 하지만 실제로는 쓰이지 않는 객체
      • 자리 채움용
    • 테스트 스텁(Test Stub)
      • SUT가 의존하는 실제 컴포넌트를 대신해 테스트가 SUT의 간접 입력을 제어할 수 있게 해주는 객체
      • 테스트 스파이(Test Spy)
        • 테스트 스텁의 업그레이드 버전, SUT의 실행이 끝난 후 테스트가 자신을 검사해 SUT의 간접 출력을 검증할 수 있게 한다.
    • 모의 객체(Mock Object)
      • SUT가 의존하는 실제 컴포넌트를 대체해 테스트가 SUT의 간접 출력을 검증할 수 있게 한다.
    • 가짜 객체(Fake Object)
      • 실제 DOC의 기능을 다르게 구현해 대체한 객체

더미 객체

  • 테스트 대역이 퇴화된 형태
  • 메소드에서 메소드로 주고받는 것 외에는 전혀 쓸모가 없다.
  • 널 객체 와는 다르다
  • SUT에서 쓰지 않으므로 더미 객체가 어떻게 동작하는지 걱정할 필요 없다.

테스트 스텁

  • 자신의 메소드가 호출될 때 SUT로 간접 입력 값을 보내는 제어 위치로 사용되는 객체
  • 응답기(Responder)
    • 메소드가 호출될 때 정상/비정상 간접 입력을 SUT로 보내주는 기본적인 테스트 스텁
  • 훼방꾼(Saboteur)
    • 예외나 에러를 발생시켜 SUT에 비정상 간접 입력을 보내주는 특수 테스트 스텁

테스트 스파이

  • SUT의 간접 출력에 대한 관찰 위치로 쓰이는 객체
  • 테스트 스텁의 기능에 더해 테스트 스파이에서는 SUT가 자신의 메소드를 호출한 내역을 기록할 수 있다.
  • 검증 단계에서는 테스트 스파이로부터 실제로 호출된 내역과 기대 호출 내역을 비교하는 절차형 동작 검증을 한다.

모의객체

  • SUT의 간접 출력에 대한 관찰 위치로 쓰이는 객체
  • 테스트 스텁과 마찬가지로 메소드가 호출되면 정보를 리턴해야 함
  • SUT가 자신을 어떻게 호출했는지 신경 씀
  • 테스트 스파이와 다른점
    • 미리 정의한 기대 값과 실제 호출을 단언문으로 비교해 문제가 있으면 테스트 메소드를 대신해 테스트를 실패 시킨다.
    • 모든 테스트에서 같은 모의 객체로 SUT의 간접 출력을 검증하는 로직을 재사용할 수 있다.
  • 엄격한 모의 객체
    • 정상 호출이여도 명시된 것과 순서가 다르면 테스트 실패
  • 관대한 모의 객체
    • 호출 순서가 달라도 넘어감.
    • 실제로 호출된 메소드에 대해서만 그에 해당하는 기대 호출을 검증

가짜 객체

  • 테스트에 의해 제어되지 않고 관찰되지도 않는다.
  • 실제 DOC의 기능 중 전체나 일부를 단순하게 구현
  • 가짜 객체 쓰는 이유
    • 너무 느리거나, 테스트 환경에서 쓸 수 없을 때

테스트 대역 제공하기

  • 테스트 제공하는 방법 두가지
    • 직접 만든 테스트 대역
      • 하드 코딩 한 것
      • 의사 객체(기본 상위 클래스 집합)
        • 테스트 스텁, 테스트 스파이, 모의 객체에서 호출 될 것으로 예상 되는 여러 메소드를 한데 모은다.
    • 동적 생성 테스트 대역
      • 다른 개발자가 제공하는 프레임워크나 툴킷으로 실행 시간에 생성하는 것

테스트 대역 설정하기

  • 테스트 스텁, 모의 객체
    • 기대 값이 무엇인지, 어떤 값을 리턴해야 하는지 알려줘야 한다.
  • 테스트 스텁, 테스트 스파이
    • SUT가 호출할 메소드가 리턴하는 값을 설정
  • 모의 객체
    • SUT가 호출할 것으로 예상하는 모든 메소드의 이름과 인자를 설정
  • 가짜 객체, 더미 객체
    • 설정할 필요 없음

테스트 대역 설치하기

  • SUT에게 테스트 대역을 쓰게 알려주는 것
  • 테스트 대역 설치 순서
    1. 테스트 대역 생성
    2. 설정되는 테스트 대역이라면 필요한 설정하기
    3. SUT 실행하기 전이나 실행할 때 테스트 대역을 대신 사용하게 만들기
  • 테스트 대역 설치 방법
    • 의존 주입 : 클라이언트 소프트웨어가 SUT에게 어떤 DOC를 쓸지 알려준다.
      • Setter method로 주입
      • 생성자에서 주입
      • 인자로 주입
    • 의존 찾기 : SUT가 DOC의 생성/얻기를 다른 객체에 위임
      • 객체 팩토리
        • SUT에서 DOC를 직접 생성하지 않고 대신 잘 알고 있는 객체에 있는 팩토리 매소드를 호출해 DOC 생성
      • 서비스 탐지기
        • SUT는 잘 알고 있는 레지스트리 객체를 통해 이전에 만들어진 서비스 객체를 얻는다.
    • 테스트 훅 : SUT 내에서 DOC나 DOC로의 호출을 변경
      • 레거시 코드에 테스트를 도입할 때 위에 기법들이 도입하기 어려울 때 사용

테스트 대역의 다른 용도

내시경 테스팅

  • 모의 객체를 테스트 대상 메소드의 인자로 넘겨 SUT를 안에서 테스트 한다.
    • 밖에서는 보이지 않는 SUT의 특정 내부 동작도 검증 가능

필요 주도 개발

  • 내시경 테스팅이 발전한 형태
  • 테스트를 작성하면서 동시에 SUT의 의존을 정의
  • ‘밖에서 안으로’ 작성하고 테스트하는 방법

픽스처 설치 빠르게 하기

  • 테스트 대역은 신선한 픽스처의 설치 시간을 줄일 때도 쓰인다.
  • 엔티티 체인 스니핑
    • 여러 객체의 집합 대신 단일 테스트 대역을 생성

테스트 실행 빠르게 하기

  • 가짜 데이터베이스 구현

기타 고려 사항

  • 실제 DOC가 제대로 설치됐는지 검증
    • 생성자 테스트
  • 새로 산 망치의 오류
    • 남용하게 되는 심하게 명세된 소프트웨어가 된다.