테스트의 목적은 코드를 실행하거나 Coverage를 높이는 것이 아니라, 시스템이 지켜야 할 속성을 위반하는 반례를 찾는 것입니다. 테스트 결과가 반복 가능하고 실패 원인이 분명해야 배포 판단에 사용할 수 있습니다.

검증할 속성을 먼저 정한다

  • 각 테스트는 입력 사례보다 먼저 지켜야 할 invariant와 관찰 방법을 정합니다. Assert 없이 실행만 하는 테스트는 검증이 아닙니다.
  • 테스트 데이터는 실제 또는 예상 사용 환경에서 들어올 입력을 닮아야 합니다. 탐지·분류 기능은 명백한 정상·문제 사례뿐 아니라, 공격 문구를 인용한 보안 문서처럼 문제로 오인하기 쉬운 정상 사례도 넣어 오탐과 누락을 함께 확인합니다.
  • 내부 상태와 사용자에게 보이는 최종 outcome을 모두 봅니다. 내부 상태가 정상이어도 데이터 누락이나 잘못된 Side Effect가 남을 수 있습니다.
  • Mock은 내가 가정한 동작만 증명합니다. 외부 서비스와의 실제 경계는 Contract Test로 별도 검증합니다.
  • Coverage는 실행하지 않은 코드를 찾는 지표일 뿐 테스트의 정확성을 증명하지 않습니다. 중요한 변경에서는 작은 결함을 의도적으로 넣어 테스트가 실패하는지 확인하는 Mutation Testing을 선택적으로 사용합니다.

희귀한 실패를 자주 발생시킨다

  • 입력뿐 아니라 Operation 순서, 설정, 동시성, 실행 시간도 무작위화합니다. 실패하면 seed와 실행 조건을 보존합니다.
  • 모든 기능을 매번 켜면 서로 상태를 상쇄하거나 제한된 실행 안에서 각 경로가 얕게 탐색될 수 있습니다. Swarm Testing은 실행마다 일부 Feature나 Operation을 제외하여 다른 상태 공간을 깊게 탐색합니다.
  • Buggification은 테스트에서 실제 계약상 가능한 오류를 의도적으로 주입하여 Retry·Fallback·복구 경로를 반복 검증합니다. Production에 존재할 수 없는 실패를 만들어서는 안 됩니다.
  • 긴 Timeout, 대용량 임계값, 드문 Background Job은 테스트 환경에서 축소합니다. 의미를 바꾸지 않으면서 Failover·Shard 분할·만료 같은 경로가 실행 시간 안에 발생하도록 합니다.

검증 시점을 나눈다

  • 마지막에만 검사하면 중간에 깨졌다가 우연히 복구된 상태를 놓칩니다. 작업과 invariant 검증을 번갈아 실행합니다.
  • 데이터 손상처럼 한순간도 허용할 수 없는 Safety는 계속 검사합니다.
  • 장애 후 복구처럼 시간이 필요한 Liveness는 정해진 기한 안에 결국 성립하는지 검사합니다.
  • 종료 후에만 의미가 있는 일관성·정리 상태는 작업이 멈춘 뒤 별도로 검사합니다.

실패를 재현 가능한 자산으로 만든다

  • Random seed, 설정, Feature 조합, Fault와 Operation 순서를 함께 남깁니다. 재실행할 수 없는 실패는 수정 여부도 검증할 수 없습니다.
  • 실패 시퀀스는 원인을 유지하는 최소 사례로 줄입니다. Property-based Testing의 shrinking이나 단계 제거로 불필요한 이력을 없앱니다.
  • 수정한 실패는 고정된 Regression Test로 남깁니다. 무작위 탐색이 같은 반례를 다시 찾기를 기다리지 않습니다.

테스트 신호를 보호한다

  • 같은 코드가 통과와 실패를 반복하는 Flaky Test는 테스트 결함 또는 실제 비결정성의 증거입니다. 단순 재시도로 숨기지 않습니다.
  • 시간, 난수, 파일, 전역 상태와 외부 네트워크를 통제하여 테스트를 격리합니다. 격리 때문에 실제 경계를 대체했다면 별도 Contract Test로 그 가정이 맞는지 확인합니다.
  • 검증하려는 속성을 가장 싸고 좁게 깨뜨릴 수 있는 테스트를 선택합니다. 전체 시스템의 협력 결과가 대상일 때만 E2E 범위를 사용합니다.

References