개발

단위 테스트 2장

데굴데굴. 2023. 1. 25. 18:25

책 '단위 테스트'를 읽고 정리한 내용입니다.

2장 단위 테스트란 무엇인가

2.1 '단위 테스트'의 정의

  • 단위 테스트의 세 가지 속성
    1. 작은 코드 조각(단위라고도 함)을 검증하고,
    2. 빠르게 수행하고,
    3. 격리된 방식으로 처리하는 자동화된 테스트다.
  • 세 가지 속성 중 '격리'가 무엇인지에 대한 의견 차이로 고전파와 런던파가 시작되었다.2.1.1 격리 문제에 대한 런던파의 접근
  • 런던파에서의 '격리': 테스트 대상 시스템을 협력자에게서 격리하는 것
  • 의존성을 테스트 대역으로 대체해서 테스트 대상 클래스에만 집중할 수 있다.
  • 장점
    • 테스트가 실패하면 어느 부분이 문제인지 확실히 알 수 있다. (= 테스트 대상에 문제가 있는 것을 바로 알 수 있다.)
    • 객체 그래프(같은 문제를 해결하는 클래스의 통신망)를 분할할 수 있다. -> 특정 문제에 대한 의존성을 분리해서 볼 수 있다는 의미인듯
    • 테스트 대상의 의존성, 그 의존성의 의존성을 다룰 필요가 없어 객체 그래프를 다시 만들지 않아도 된다.
    • 한 번에 한 클래스만 테스트하라는 지침을 도입하면 전체 단위 테스트 스위트의 구조가 간단해진다.
  • 목 프레임워크: C# Moq, NSubstitute / JAVA Mockito, JMock, EasyMock
  • 테스트 대역은 실행과 관련 없이 모든 종류의 가짜 의존성을 설명하는 포괄적인 용어이며, 목은 그러한 의존성의 한 종류일 뿐이다.2.1.2 격리 문제에 대한 고전파의 접근
  • 코드를 꼭 격리하는 방식일 필요는 없지만, 단위 테스트는 서로 격리해서 실행해야 한다.
    => 여러 단위 테스트가 실행 순서에 상관 없이 항상 성공해야 한다.
  • 의존성의 종류
    • 공유(shared) 의존성: 테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성 (예: static mutable field, DB, 파일 시스템)
    • 비공개(private) 의존성: 공유하지 않는 의존성
    • 프로세스 외부(out-of-process) 의존성: 애플리케이션 실행 프로세스 외부에서 실행되는 의존성
  • 휘발성 의존성
    • 개발자 머신에 기본 설치된 환경 외에 런타임 환경의 설정 및 구성을 요구한다. 예: 데이터베이스, API 서비스
    • 비결정적 동작을 포함한다. 예: 난수 생성기, 현재 날짜와 시간을 반환하는 클래스
  • 테스트 간에 공유 상태를 일으키는 공유 의존성에 대해서는 테스트 간에 공유되거나 테스트 대역으로 대체되어야 한다.
    • 테스트마다 공유 의존성을 만들 수 없기 때문
    • 공유 의존성은 실행 프로세스 외부에 있는 경우가 많아서 호출이 오래 걸리기 때문 => 빠르게 수행되어야 한다는 전제에 어긋남
  • 고전파는 단위가 반드시 클래스가 국한될 필요가 없다고 본다. => 공유 의존성이 없는 여러 클래스가 단위가 될 수 있다고 생각한다.2.2 단위 테스트의 런던파와 고전파
  • 격리 특성에 대한 각 분파의 생각
    • 런던파: 테스트 대상 시스템에서 협력자를 격리하는 것
    • 고전파: 단위 테스트끼리 격리하는 것
격리 주체 단위의 크기 테스트 대역 사용 대상
런던파 단위 단일 클래스 불변 의존성 외 모든 의존성
고전파 단위 테스트 단일 클래스 또는 클래스 세트 공유 의존성

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

공유 의존성 변경 가능한 비공개 의존성 불변인 의존성
런던파 교체 대상 교체 대상 교체 대상 아님
고전파 교체 대상 교체 대상 아님 교체 대상 아님
  • 협력자와 값 객체
    • 협력자: 공유하거나 변경 가능한 의존성
    • 값 객체: 불변 객체
  • 공유 의존성과 프로세스 외부 의존성
    • 공유 의존성이지만 프로세스 외부 의존성이 아닌 예: 싱글턴, 클래스의 정적 필드
    • 공유 의존성이면서 프로세스 외부 의존성인 예: 데이터베이스 (<-변경 가능)
    • 공유 의존성이 아니지만 프로세스 외부 의존성인 예: 읽기 전용 API 서비스 (<-불변)
    • 실제 프로젝트에서 프로세스 외부가 아닌 공유 의존성은 거의 없다.

2.3 고전파와 런던파의 비교

  • 목을 사용하는 테스트는 고전적인 테스트보다 불안정한 경향이 있다. 필자는 개인적으로 고전파를 선호한다고 한다.
  • 런던파의 장점
    • 입자성이 좋다. 테스트가 세밀해서 한 번에 한 클래스만 확인한다.
    • 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다. 모든 협력자는 테스트 대역으로 대체되기 때문에 테스트 작성 시 걱정할 필요가 없다.
    • 테스트가 실패하면 어떤 기능이 실패했는지 확실히 알 수 있다. 클래스의 협력자가 없으면 테스트 대상 클래스 외에 다른 것을 의심할 여지가 없다.

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

  • 런던파는 클래스 단위로 단위 테스트를 한다.
  • 하지만 테스트는 코드의 단위가 아닌 동작의 단위를 검증해야 한다. 즉, 단위는 여러 클래스에 걸쳐 있거나 한 클래스에만 있거나 아주 작은 메서드가 될 수도 있다.

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

  • 의존성 그래프가 복잡할 때 목을 사용하면 테스트가 쉽다. (런던파)
  • 하지만, 먼저 복잡한 그래프를 갖지 않는 것에 집중해야 한다. 클래스 그래프가 커진 것은 코드 설계 문제이다.

2.3.3 버그 위치 정확히 찾아내기

  • 런던 스타일 테스트를 사용하면 SUT에 버그가 포함된 테스트만 실패하지만, 고전적인 방식이면 전체 시스템에 걸쳐 테스트 실패를 야기할 수 있다.
  • 고전적인 방식의 이러한 문제점을 해결하기 위해서는 테스트를 정기적으로(이상적으로는 소스 코드가 변경될 때마다) 실행하면 버그의 원인을 알아낼 수 있다.
  • 테스트 스위트 전체에 걸쳐 실패하는 것도 가치가 있다. 그만큼 중요한 부분에 버그가 있다는 뜻이기 때문.

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

  • 테스트 주도 개발을 통한 시스템 설계 방식
    • 테스트 주도 개발의 3단계
      1. 추가해야 할 기능과 어떻게 동작해야 하는지를 나타내는 실패 테스트를 작성한다.
      2. 테스트가 통과할 만큼 충분히 코드를 작성한다. 이 단계에서 코드가 깨끗하거나 명쾌할 필요는 없다.
      3. 코드를 리팩터링한다. 통과 테스트 보호하에서 코드를 안전하게 정리해 좀 더 읽기 쉽고 유지하기 쉽도록 만들 수 있다.
    • 런던파: 하향식 TDD. 전체 시스템에 대한 기대치를 설정하는 상위 레벨 테스트부터 시작한다.
    • 고전파: 상향식 TDD. 도메인 모델부터 시작한다.
  • 과도한 명세 문제
    • 런던파가 고전파보다 테스트가 구현에 자주 결합되는 편이다.

2.4 두 분파의 통합 테스트

  • 런던파의 통합 테스트: 실제 협력자 객체를 사용하는 모든 테스트를 통합 테스트로 간주한다.
  • 고전파의 통합 테스트
    • 단위 테스트의 3가지 속성 (단일 동작 단위 검증, 빠르게 수행, 다른 테스트와 별도로 처리) 중 하나 이상을 충족하지 않는 테스트
      • '다른 테스트와 별도로 처리' 미충족: 공유 의존성에 접근하는 테스트
      • '빠르게 수행' 미충족: 프로세스 외부 의존성에 접근하는 테스트
      • '단일 동작 단위 검증' 미충족: 둘 이상의 동작 단위를 검증하는 테스트, 다른 팀이 개발한 모듈이 둘 이상 있을 때 한번에 검증하는 테스트
  • 통합 테스트란? 공유 의존성, 프로세스 외부 의존성뿐 아니라 조직 내 다른 팀이 개발한 코드 등과 통합해 작동하는지도 검증하는 테스트!

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

  • 엔드 투 엔드 테스트
    • 통합 테스트의 일부. 프로세스 외부 종속성과 함께 어떻게 작동하는지 검증한다.
    • 일반적인 통합 테스트보다 의존성을 더 많이 포함한다.
    • 동의어: UI 테스트, GUI 테스트, 기능 테스트

요약

  • 단위 테스트의 정의
    • 단일 동작 단위를 검증하고
    • 빠르게 수행하고
    • 다른 테스트와 별도로 처리한다.
  • 격리 논쟁으로 고전파와 런던파 두 개의 단위 테스트 분파가 나뉘었다.
    • 런던파의 격리: 테스트 대상 단위를 분리해야 한다. 불변 의존성을 제외한 모든 의존성을 테스트 대역으로 대체해야 한다.
    • 고전파의 격리: 단위가 아니라 단위 테스트를 서로 분리해야 한다. 테스트 대상 단위는 코드 단위가 아니라 동작 단위이다. 따라서 공유 의존성만 테스트 대역으로 대체해야 한다.
  • 런던파의 장점
    • 더 나은 입자성
    • 상호 연결된 클래스의 큰 그래프에 대한 테스트 용이성
    • 테스트 실패 후 버그가 있는 기능을 쉽게 찾을 수 있는 편의성
  • 런던파의 문제점
    • 테스트 대상 클래스에 대한 초점이 잘못됐다. 테스트는 코드 단위가 아니라 동작 단위를 검증해야 한다.
    • 코드 조각을 단위 테스트 할 수 없다는 것은 코드 설계에 문제가 있는 것이고, 테스트 대역 사용은 이 문제를 오히려 숨긴다.
    • 테스트가 실패해도 버그의 원인을 찾는 것이 그리 어렵지 않다. 마지막에 수정한 것이 버그의 원인이기 때문이다.
    • 런던파의 가장 큰 문제점은 과잉 명세, 즉 SUT 세부 구현에 결합된 테스트 문제다.
  • 통합 테스트: 단위 테스트 기준 중 하나 이상을 충족하지 못하는 테스트
  • 엔드 투 엔드 테스트: 통합 테스트의 일부. 최종 사용자의 관점에서 시스템을 검증한다. 프로세스 외부 의존성의 전부 또는 대부분에 직접 접근한다.

'개발' 카테고리의 다른 글

단위 테스트 3장  (0) 2023.01.29