Summary#
Property-based test harness는 “무작위 입력을 많이 던지는 테스트”가 아니라, 생성기 설계, 제약 조건, shrinking, seed 재현성, oracle 신뢰성을 함께 다루는 테스트 아키텍처다. 좋은 harness는 입력 공간을 넓게 탐색하면서도 도메인 불변식을 보존하고, 실패 시 재현 가능한 seed와 최소 반례를 남기며, 테스트 oracle 자체의 플래키성을 분리해 진단할 수 있어야 한다.
이 초안은 공개 웹 검색/검증 도구가 현재 세션에 제공되지 않은 상태에서 작성한 capsule 초안이다. 따라서 아래 Sources는 신뢰 가능한 공개 문서로 알려진 URL 후보이며, 최신 내용과 정확한 문구는 별도 WebSearch/WebFetch로 재검증해야 한다.
Key Points#
- Generator constraints는 입력 공간을 줄이는 동시에 bias를 만든다
- Property-based testing에서 generator는 테스트의 실제 탐색 범위를 결정한다.
filter,assume, precondition 기반 제약은 구현이 쉽지만, 거부율이 높으면 유효 사례가 거의 생성되지 않아 탐색 품질이 떨어질 수 있다.- 가능하면 “생성 후 필터링”보다 “처음부터 유효한 값을 생성하는 constructive generator”가 낫다.
-
예: 정렬 함수 테스트에서 임의 리스트를 만들고 정렬 여부를 확인하는 것은 쉽지만, 파서/직렬화 테스트에서는 문법적으로 유효한 AST를 직접 생성하는 편이 실패 밀도와 shrinking 품질 모두에 유리하다.
-
Generator distribution은 테스트가 발견하는 버그의 종류를 편향한다
- 균등 랜덤은 실제로는 좋은 기본값이 아닐 수 있다.
- 경계값, 빈 컬렉션, 중복값, 매우 큰 값, Unicode, NaN, timezone, overflow 근처 값 등은 명시적으로 높은 확률로 포함해야 한다.
- 상태 기반 테스트에서는 operation sequence의 길이, operation mix, 사전조건 만족률이 중요하다.
-
generator coverage metric 또는 event labeling을 통해 어떤 케이스가 실제로 생성되는지 관찰하는 것이 좋다.
-
Shrinking semantics는 “최소 반례”의 의미를 결정한다
- Shrinking은 실패한 입력을 더 단순한 입력으로 줄여 사람이 이해 가능한 counterexample을 만드는 과정이다.
- 하지만 “작다”의 의미는 도메인마다 다르다.
- 숫자는 0에 가까운 값이 단순할 수 있다.
- 리스트는 짧은 길이가 단순할 수 있다.
- 상태 machine 테스트에서는 짧은 command sequence가 단순할 수 있다.
- AST에서는 노드 수, depth, literal complexity 등이 단순성 기준이 될 수 있다.
- 잘못 설계된 shrinker는 불변식을 깨뜨리거나, 실제 원인을 가리는 반례로 수렴하거나, 너무 느리게 동작할 수 있다.
-
따라서 generator와 shrinker는 분리된 부품이 아니라 같은 도메인 모델을 공유해야 한다.
-
Seed reproducibility는 실패 triage의 핵심이다
- Property-based test 실패는 반드시 재현 가능해야 한다.
- 실패 로그에는 최소한 다음 정보가 남아야 한다.
- framework 이름과 버전
- seed 또는 replay token
- shrinking 이후 최소 반례
- shrinking 이전 원본 입력이 필요한 경우 원본 입력
- 관련 설정: max examples, deadline/timeout, database 사용 여부, stateful step count
- CI에서는 실패 seed를 artifact로 보존하고, 로컬 재현 명령을 함께 출력해야 한다.
-
일부 framework는 실패 예제를 example database에 저장하므로, 로컬/CI 간 database 차이가 재현성에 영향을 줄 수 있다.
-
Flaky oracle은 property-based testing에서 특히 위험하다
- 입력이 랜덤하게 바뀌기 때문에 실패가 generator 문제인지, 코드 문제인지, oracle 문제인지 구분하기 어려워진다.
- flaky oracle의 흔한 원인:
- 시간 의존성: 현재 시각, timeout, sleep
- 비결정적 I/O: 네트워크, 파일 시스템 race, 외부 API
- concurrency race
- 전역 상태 오염
- 랜덤을 사용하는 production code와 test generator seed가 분리되지 않은 경우
- floating point tolerance가 너무 엄격한 경우
- snapshot/golden output이 환경에 따라 달라지는 경우
- property는 가능하면 deterministic pure oracle로 작성해야 한다.
-
외부 시스템을 포함해야 한다면 fake clock, deterministic scheduler, fixed RNG, isolated temp directory, mock server를 사용한다.
-
Stateful property-based testing은 harness 구조가 더 중요하다
- 단일 입력 함수 테스트와 달리 stateful testing은 operation sequence를 생성하고, model과 system under test를 비교한다.
- 핵심 구성요소:
- command generator
- command precondition
- model state transition
- real system invocation
- invariant 또는 postcondition
- sequence shrinking
- 좋은 stateful harness는 “가능한 모든 sequence”보다 “의미 있는 상태 전이”를 잘 생성해야 한다.
-
실패 시에는 전체 sequence, shrinked sequence, 각 step의 model/SUT 상태 차이를 로깅해야 한다.
-
Harness architecture 권장 구조
Domain Model- 유효한 값, 상태, 불변식 정의
Generators- primitive generator
- composite generator
- edge-case weighted generator
- invalid-input generator, 필요한 경우
Shrinkers- 도메인 불변식을 유지하는 shrink rule
- sequence shrink rule
Properties- invariant
- metamorphic property
- round-trip property
- differential property
- model-based property
Oracle Layer- deterministic checker
- tolerance policy
- external dependency isolation
Reproducibility Layer- seed capture
- replay command
- failing example persistence
- CI artifact
-
Flakiness Guard- retry는 진단용으로만 사용
- timeout/deadline 기록
- nondeterministic dependency 차단
- flaky failure quarantine policy
-
Good properties의 예
- Round-trip:
decode(encode(x)) == x
- Metamorphic:
sort(xs) == sort(reverse(xs))
- Idempotence:
normalize(normalize(x)) == normalize(x)
- Differential:
- 새 구현 결과가 reference implementation과 같아야 함
-
Model-based:
- 실제 cache 구현의 observable behavior가 단순 map model과 일치해야 함
-
Anti-patterns
- 지나친
assume사용으로 대부분의 입력을 버림 - generator가 production fixture 몇 개만 반복 생성함
- seed를 로그에 남기지 않음
- shrinking된 counterexample만 남기고 원본 failure context를 잃음
- oracle이 현재 시간, 네트워크, thread scheduling에 의존함
- property가 구현 세부사항을 그대로 복제해 같은 버그를 공유함
- CI failure를 “랜덤 테스트니까 가끔 실패함”으로 취급함
Cautions#
- 현재 실행 환경에는 사용자가 요구한
WebSearch및WebFetch도구가 제공되지 않았다. 따라서 실제 공개 웹 검색을 수행하지 못했다. - 아래 Sources는 널리 알려진 공식 문서/논문 URL 후보이며, 최종 private capsule 등록 전 최신 URL, 문서 내용, 인용 문구를 별도 검증해야 한다.
- framework별 seed/replay 동작은 버전마다 다를 수 있다. Hypothesis, QuickCheck, ScalaCheck, jqwik, fast-check 등은 재현성·shrinking·example database 정책이 서로 다르므로 특정 구현 세부사항을 일반화하면 안 된다.
- shrinking 결과는 항상 “원인의 최소 형태”를 의미하지 않는다. 단지 해당 framework의 단순성 ordering에서 더 작은 failing example일 수 있다.
- flaky oracle 문제는 retry로 숨기면 안 된다. retry는 재현성 진단 보조 수단일 수 있으나, deterministic harness 설계를 대체하지 못한다.
Sources#
- https://hypothesis.readthedocs.io/en/latest/
- https://hypothesis.readthedocs.io/en/latest/stateful.html
- https://hypothesis.readthedocs.io/en/latest/reproducing.html
- https://hypothesis.readthedocs.io/en/latest/settings.html
- https://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html
- https://www.cse.chalmers.se/~rjmh/Papers/quickcheck.pdf
- https://scalacheck.org/documentation.html
- https://github.com/dubzzz/fast-check
Related#
- Core API Idempotency-Key Contracts: Request Fingerprinting, Replay Semantics, Concurrent Duplicate Suppression, and Expiry Failure Modes
- Event Sourcing Snapshot and Upcaster Architecture: Snapshot Versioning, Historical Event Migration, Replay Boundaries, and Rebuild Cutover Failure Modes
- Update Failure Modes
Sagwan Revalidation 2026-09-02T10:29:11Z#
- verdict:
refresh - note: 핵심 원칙은 유효하나 출처 미검증 전제가 남아 공개본 재검증 가치가 큼.
Sagwan Revalidation 2026-09-08T13:08:40Z#
- verdict:
ok - note: [chatgpt HTTP 404] {
Sagwan Revalidation 2026-09-11T02:27:23Z#
- verdict:
ok - note: 개념·권장안이 최신 PBT 관행과 부합하며 즉시 갱신 필요 없음
Sagwan Revalidation 2026-09-13T22:35:01Z#
- verdict:
ok - note: 생성기 제약·shrinking semantics·seed 재현성 개념은 업계 표준으로 굳어진 내용이며, 특정 버전/API 의존 없이 여전히 유효하다.