분명 로컬 환경에서는 모든 테스트 케이스를 통과했는데 배포 직후 예상치 못한 입력값 하나에 서버가 터졌다. 개발자 커뮤니티에서는 매번 수동으로 엣지케이스를 짜넣는 단위 테스트의 한계를 토로하는 목소리가 높다. 최근 깃허브의 테스트 라이브러리 생태계에서는 사람이 생각하지 못한 입력 조합을 AI처럼 스스로 생성해 버그를 찾아내는 방식이 다시 뜨겁게 논의되고 있다.

Hypothesis와 pytest를 활용한 5가지 검증 체계

이번에 공개된 가이드는 Hypothesis(속성 기반 테스트를 수행하는 파이썬 라이브러리)와 pytest(파이썬의 표준 테스트 프레임워크)를 결합해 견고한 테스트 파이프라인을 구축하는 방법을 다룬다. 환경 구축을 위해 다음과 같은 설치 과정을 거친다.

bash
pip install hypothesis pytest

테스트의 기초가 되는 유틸리티 함수로는 clamp(값을 특정 범위 내로 제한하는 함수), normalize_whitespace(공백을 표준화하는 함수), merge_sorted(정렬된 두 리스트를 합치는 함수) 등이 정의된다. 이후 int_like_strings(정수 형태의 문자열을 생성하는 전략)와 같은 복합 전략을 통해 입력 공간을 정밀하게 제어한다.

검증 단계는 총 다섯 가지 핵심 기법으로 나뉜다. 첫째는 Invariants(어떤 입력이 들어와도 항상 참이어야 하는 성질)를 정의해 경계 조건과 결정론적 정규화를 확인하는 속성 테스트다. 둘째는 Differential Testing(두 개의 서로 다른 구현체를 비교해 결과가 같은지 확인하는 테스트)으로, 구현한 merge 함수가 신뢰할 수 있는 참조 모델과 일치하는지 검증한다. 셋째는 Targeted Exploration(특정 영역을 집중 탐색하는 기법)을 통해 두 개의 독립적인 정수 파서가 동일한 입력에 대해 일치하는 결과를 내는지 확인하고 잘못된 문자열을 정확히 거부하는지 체크한다.

넷째는 Metamorphic Testing(입력을 변형했을 때 결과가 어떻게 변해야 하는지 검증하는 테스트)으로, 변환 후에도 유지되어야 하는 분산의 불변성을 확인한다. 마지막 다섯째는 Stateful Testing(시스템의 상태 변화를 추적하며 일련의 동작을 검증하는 테스트)이다. 이는 Hypothesis의 규칙 기반 상태 머신을 이용해 은행 계좌 시스템을 시뮬레이션하며, 임의의 작업 순서 속에서도 잔액 일관성과 원장 무결성이 유지되는지 검증하는 방식으로 진행된다.

예제 기반 테스트에서 행동 기반 검증으로의 전환

기존의 단위 테스트는 개발자가 예상한 특정 입력값에 대해 특정 결과가 나오는지를 확인하는 예제 기반 방식이었다. 이 방식의 치명적인 약점은 개발자가 상상하지 못한 엣지케이스는 영원히 발견되지 않는다는 점이다. 반면 Hypothesis가 도입한 속성 기반 테스트는 입력값 자체를 정의하는 것이 아니라 입력이 가져야 할 속성을 정의한다.

여기서 가장 강력한 기능은 Shrinking(실패하는 입력값 중 가장 단순한 사례를 찾아내는 과정)이다. 수천 개의 무작위 입력 중 버그를 유발하는 값을 찾으면, 라이브러리가 이를 자동으로 깎아내어 개발자가 즉시 수정할 수 있는 최소 단위의 반례를 제시한다. 이는 디버깅 시간을 획기적으로 줄여주는 핵심 장치다.

또한 Differential Testing과 Metamorphic Testing의 결합은 정답지를 만들기 어려운 복잡한 알고리즘 검증에 답을 제시한다. 완벽한 정답 함수를 짤 수 없다면, 기존의 신뢰할 수 있는 라이브러리와 비교하거나 입력값을 살짝 바꿨을 때 결과가 어떻게 변해야 한다는 규칙을 세워 검증하는 식이다. 상태 기반 테스트 역시 단순한 함수 호출을 넘어 시스템의 생명주기 전체에서 발생할 수 있는 레이스 컨디션이나 논리적 모순을 찾아내는 데 최적화되어 있다.

이제 테스트 코드를 짜는 일은 정답지를 만드는 과정이 아니라 시스템이 지켜야 할 철학적 불변성을 정의하는 설계 작업으로 변하고 있다.