‘단위 테스트’의 정의

  • 단위 테스트는
    • 작은 코드 조각을 검증하고,
    • 빠르게 수행하고,
    • 격리된 방식으로 처리하는 자동화된 테스트다.
  • 격리 문제는 단위 테스트의 고전파와 런던파를 구분할 수 있게 해주는 근원적 차이에 속한다.

격리 문제에 대한 런던파의 접근

  • 런던파에서는 테스트 대상 시스템을 협력자에게서 격리한다.
    • 하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역(test double)으로 대체해야 한다.
  • 장점
    • 테스트가 실패하면 코드베이스의 어느 부분이 고장 났는지 확실히 알 수 있다.
    • 객체 그래프를 분할할 수 있다. 테스트 대역을 사용해서 복잡한 의존성을 무시하고 객체 그래프를 만들지 않아도 된다.
    • 한 번에 한 클래스만 테스트하라는 지침을 도입하면 전체 단위 테스트 스위트를 간단한 구조로 할 수 있다.
  • 고전파는 SUT(System Under Test)와 협력자의 상태를 검증한다.
  • 런던파는 SUT의 상태, SUT와 협력자의 상호 작용을 검증한다.

격리 문제에 대한 고전파의 접근

  • 고전파에서는 코드를 격리하는 방식이 아닌, 단위 테스트를 서로 격리 시킨다.
    • 여러 클래스가 모두 메모리에 상주하고 공유 상태에 도달하지 않는 한, 여러 클래스를 한 번에 테스트해도 괜찮다.
  • 공유 의존성, 비공개 의존성, 프로세스 외부 의존성
    • 공유 의존성: 테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성
      • 정적 가변 필드, 데이터베이스 등
      • 싱글턴 인스턴스는 프로덕션 코드에서는 단 하나만 있지만, 테스트에서는 각 테스트마다 새 인스턴스를 만들 수 있으므로 공유 의존성이 아니다.
    • 비공개 의존성: 공유하지 않는 의존성
    • 프로세스 외부 의존성: 애플리케이션 실행 프로세스 외부에서 실행되는 의존성이며, 아직 메모리에 없는 데이터에 대한 프록시다.
      • 데이터베이스는 프로세스 외부 이면서 공유 의존성
      • 그러나 각 테스트 전에 도커 컨테이너로 데이터베이스를 시작하면 프로세스 외부이면서 공유하지 않는 의존성
  • 보통 테스트 간에 공유 상태를 일으키는 의존성에 대해서만 테스트 대역을 사용한다.
    • 공유 의존성은 일반 실행 프로세스 외부에 있어서, 테스트 대역을 사용하여 테스트 실행속도를 높일 수 있다.
    • 프로세스 외부 의존성을 가진 테스트는 단위 테스트 영역에서 통합 테스트 영역으로 넘어간다.

단위 테스트의 런던파와 고전파

  • 런던파와 고전파는 세 가지 주요 주제에 대해 의견 차이가 있다.
    • 격리 요구 사항
    • 테스트 대상 코드 조각의 구성 요소
    • 의존성 처리
격리 주체단위의 크기테스트 대역 사용 대상
런던파단위단일 클래스불변 의존성 외 모든 의존성
고전파단위 테스트단일 클래스 또는 클래스 세트공유 의존성

고전파와 런던파가 의존성을 다루는 방법

  • 런던파에서 불변 객체(또는 VO)는 테스트 대역으로 교체하지 않아도 된다.
  • 비공개 의존성은 변경 가능하거나 불변일 수 있다.
    • 협력자: 공유하거나 변경 가능한 의존성이다.

고전파와 런던파 비교

  • 런던파의 이점
    • 입자성이 좋다. 테스트가 세밀해서 한 번에 한 클래스만 확인한다.
    • 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다.
    • 테스트가 실퍃면 어떤 기능이 실패했는지 확실히 알 수 있다.
  • 하지만 런던파인 ‘목을 사용하는 테스트’는 고전적인 테스트보다 불안정한 경향이 있다.

한 번에 한 클래스만 테스트하기

  • 객체지향 프로그래밍 경력을 가진 개발자들은 보통 클래스를 모든 코드베이스의 기초에 위치한 원자 빌딩 블록으로 간주한다.
    • 이런 경향은 오해의 소지가 있다.
  • 테스트는 코드의 단위를 검증해서는 안된다. 오히려 동작의 단위, 즉 문제 영역에 의미가 있는 것, 이상적으로는 비즈니스 담당자가 유용하다고 인식할 수 있는 것을 검증해야 한다.
  • 단일 동작 단위보다 적은 것을 테스트 단위로 삼는다면 사실 단위 테스트를 훼손하는 결과를 가져온다.
    • 테스트가 무엇을 검증하는지 정확히 이해하기 어려워지기 때문이다.
    • 예: ‘우리집 강아지를 부르면, 바로 나에게 온다.’ vs ‘우리집 강아지를 부르면 먼저 왼쪽 앞다리를 움직이고, 머리를 돌리고, 꼬리를 흔들기 시작한다.’

상호 연결된 클래스의 큰 그래프를 단위 테스트하기

  • 여러 계층에 걸쳐서 계속되는 식으로 의존성 그래프가 복잡하게 있을 때, 목을 사용하면 쉽게 테스트할 수 있다.
  • 테스트 대역을 쓰면 클래스이 직접적인 의존성을 대체해 그래프를 나눌 수 있으며, 이는 단위 테스트를 준비해야 할 작업량을 크게 줄일 수 있다.
  • 이 추리 과정은 잘못된 문제에 초점을 맞추고 있다.
    • 상호 연결된 클래스의 크고 복잡한 그래프를 테스트할 방법을 찾는 대신, 먼저 이러한 클래스 그래프를 갖지 않는 데 집중해야 한다. (코드 설계 문제)

버그 위치 정확히 찾아내기

  • 런던 스타일 테스트가 있는 시스템에 버그가 생기면, 보통 SUT에 버그가 포함된 테스트 실패한다.
  • 고전 방식은, 오작동하는 클래스를 참조하는 클라이언트를 대상으로 하는 테스트도 실패할 수 있다. 즉, 하나의 버그가 전체 시스템에 걸쳐 테스트 실패를 야기하는 파급 효과를 초래한다.
  • 이는 큰 문제는 아니다. 테스트를 정기적으로 실행하고, 마지막으로 한 수정이 무엇인지만 알고 있으면 버그 원인을 쉽게 알아낼 수 있다.

고전파와 런던파 사이의 다른 차이점

  • 테스트 주도 개발을 통한 시스템 설계 방식
    • 런던 스타일: 단위 테스트는 하향식 TDD로 이어지며, 전체 시스템에 대한 기대치를 상위 레벨 테스트부터 시작한다.
      • 목을 사용해 예상 결과를 달성하고자 시스템이 통신해야하는 협력자를 지정한다.
      • 테스트할 때 SUT의 모든 협력자를 차단해 해당 협력자의 구현을 나중으로 미룰 수 있다.
    • 고전파 스타일: 테스트에서 실제 객체를 다뤄야 하기 때문에 일반적으로 상향식으로 한다.
      • 도메인 모델을 시작으로 최종 사용자가 소프트웨어를 사용할 수 있을 때까지 계층을 그 위에 더 둔다.
  • 과도한 명세(over-specification) 문제: 테스트가 SUT의 구현 세부 사항에 결합되는 문제
    • 런던 스타일은 고전 스타일보다 테스트가 구현에 더 자주 결합되는 편이다.

두 분파의 통합 테스트

  • 런던파와 고전파는 통합 테스트의 정의에도 차이가 있다.
  • 런던파는 실제 협력자 객체를 사용하는 모든 테스트를 통합 테스트로 간주한다.
    • 고전파 스타일로 작성된 대부분의 테스트는 런던파에게 통합 테스트로 느껴질 것이다.
  • 고전파 관점에서 단위 테스트는
    • 단일 동작 단위를 검증하고
    • 빠르게 수행하고
    • 다른 테스트와 별도로 처리한다.
  • 고전파 관점에서 통합테스트는 이러한 기준 중 하나를 통과하지 않는다.
    • 단일 동작 단위를 검증하고 -> 둘 이상의 동작 단위를 검증하는 테스트
    • 빠르게 수행하고 -> 외부 의존성에 접근하는 테스트
    • 다른 테스트와 별도로 처리한다. -> 공유 의존성에 접근하는 테스트
  • 다른 팀이 개발한 모듈이 둘 이상 있을 때 통합 테스트로 어떻게 작동하는지 검증할 수 있다
    • 이는 세 가지 기준 모두 충족시키지 못할 것이다.

통합 테스트의 일부인 엔드 투 엔드 테스트

  • 엔드 투 엔드 테스트와 통합 테스트 간의 차이점은 엔드 투 엔드 테스트가 일반적으로 의존성을 더 많이 포함한다는 것이다.
  • 통합 테스트는 프로세스 외부 의존성을 한두 개만 갖고 작동한다. 반면에 엔드 투 엔드 테스트는 프로세스 외부 의존성을 전부 또는 대다수 갖고 작동한다.
  • 엔드 투 엔드라는 명칭은 모든 애플리케이션을 포함해 시스템을 최종 사용자 관점에서 검증하는 것을 의미한다.
  • 엔드 투 엔드 테스트는 유지 보수 측면에서 가장 비용이 많이 들기 때문에 모든 단위 테스트와 통합 테스트를 통과한 후 빌드 프로세스 후반에 실행하는 것이 좋다.
  • 엔드 투 엔드 테스트를 하더라도 일 부 의존성의 테스트 버전이 없거나 해당 의존성을 필요한 상태로 가져오는 것이 불가능할 수 있다.
    • 따라서 여전히 테스트 대역을 사용할 필요가 있고, 통합 테스트와 엔드 투 엔드 테스트 사이에 뚜렷한 경계가 없다는 사실을 강조한다.