본문으로 건너뛰기

TDD(Test-Driven Development)

테스트 크기

크기: 소비하는 자원과 수행할 수 있는 작업

  • 작은 크기 테스트
    • 단 하나의 프로세스에서 실행되어야 합니다
    • sleep, I/O 연산 같은 블로킹 호출을 하면 안됩니다
    • 외부 시스템과의 통신을 하면 안됩니다
    • 개발하면서 수시로 호출합니다
  • 중간 크기 테스트
    • 여러 프로세스를 활용할 수 있습니다
    • 블로킹 호출을 할 수 있습니다
    • 외부 시스템과의 통신을 하면 안됩니다
  • 큰 크기 테스트
    • 여러 기기, 외부 시스템을 활용할 수 있습니다
    • 릴리즈 때만 수행합니다
정보

코드 커버리지는 작은 크기 테스트에서만 측정되는 것이 좋습니다.

테스트 범위

범위: 테스트가 검증하고자 하는 코드의 양

정보

5 줄의 코드를 포함하는 함수를 만들었을 때, 실행되는 코드의 양은 5 줄인지 몇 만 줄인지 알 수 없습니다. 하지만 검증해야할 코드는 5 줄이라고 보면 됩니다. 검증해야할 코드의 양이 범위를 나타내게 됩니다.

  • 좁은 범위 테스트
    • Unit Test
    • 독립된 클래스나 메서드의 일부 로직을 검증합니다
  • 중간 범위 테스트
    • Integration Test
    • 적은 수의 컴포넌트들 사이의 상호작용을 검증합니다
  • 넓은 범위 테스트
    • End-to-End Test
    • 시스템 사이의 상호작용을 검증합니다

테스트 코드

  • 리팩터링, 새 기능 추가, 버그 수정 시에는 기존 테스트를 변경할 일이 없어야 합니다
  • 내부 구현(Private) 대신 공개 API(Public)를 테스트합니다
    • 소수의 다른 클래스를 보조하는 함수나 클래스는 내부 구현으로 간주합니다
    • 다수의 다른 클래스를 보조하는 함수나 클래스는 공개 API로 간주합니다
    • 누구든 접근 가능한 함수나 클래스는 공개 API로 간주합니다
  • 테스트 이름은 무엇을 테스트하는지 충분히 표현될 수 있도록 짓습니다
    • should로 시작하는 이름을 쓸 수 있습니다
    • Ex) shouldNotAllowWithdrawalsWhenBalanceIsEmpty
  • 테스트를 행위별로 작성합니다
    • Given/When/Then 블록을 써서 표현합니다
    • 긴 블록은 And로 구분하여 쪼갤 수 있습니다
    • 불가피하게 여러 단계가 있는경우 When/Then이 반복 될 수 있습니다
  • 언제 끝날지 모르는 결과를 기다릴 때는 sleep으로 적당히 큰 시간을 기다리기 보다 폴링과 타임아웃을 이용해서 테스트합니다
  • 제어문을 최대한 사용하지 않습니다
  • 테스트는 최대한 직설적으로 작성합니다(예상 결과를 변수를 조합해서 만들기보단 하드코딩 하기)
  • 공유 값, 공유 셋업은 테스트를 간결하게 해주지만 테스트를 이해하기 위한 세부정보를 바로 알기 어렵게 만들 수 있으므로 주의해야합니다

테스트 대역(Test Double)

Stub, Mock

Stub: 호출하면 미리 정한 값을 반환

Mock: 호출을 어떻게 하는 지 검증

  • 테스트 대상의 내부 구현을 노출해야하는 경우가 많아집니다
  • Stub의 반환 값을 왜 그렇게 정했는지 설명이 필요할 수 있습니다
  • 원하는 반환값이나 특정 오류를 일으키고 싶을 때 적합 할 수있습니다
  • 호출이 어떻게 되는지 자체가 테스트 대상일 때 Mock을 사용할 수 있습니다
  • 시스템 외부의 상태를 변화시키는 경우 Mock을 사용할 수 있습니다(Ex. sendEmail())

Fake

Fake: 실제 구현과 비슷하게 동작하도록 구현된 대역(Ex. InMemoryRepository)

  • 실제를 사용할 수 없을 때 최선일 수 있습니다
  • 사용자가 적을 때, 유지 보수 비용이 생산성 보다 큽니다
  • 가짜는 그 자체로 다시 테스트의 대상이 될 수 있습니다
    • End-to-End 테스트 등에서 실제 구현으로 테스트할 때, 동일한 테스트를 가짜로도 수행하는 식으로 테스트할 수 있습니다

Classicist vs Mockist

Unit Test

  • 실행 시간이 짧거나 애매한 경우 실제 구현 사용
  • 실행 시간이 너무 긴 경우 테스트 대역 사용
  • 결과에 랜덤성이 포함된 비결정적 테스트를 진행하는 경우 테스트 대역 사용
  • 의존성 생성이 너무 복잡한 경우 테스트 대역 사용

Integration Test

End-to-End Test

Reference

  • Titus Winters, Tom Manshreck, Hyrum Wright, Software Engineering at Google(번역본)