테스트 선택은 주장이다: 에이전트 패치가 실제로 실행되었는지 감사하는 방법
요약
이 글은 에이전트가 테스트 패치를 적용할 때 '통과'라는 주장이 실제로 모든 테스트 스위트를 포괄하는지 감사하는 방법을 다룹니다. 단순히 통과한 개수만 보고하는 것이 아니라, 선택(selected), 실행(executed), 수집(collected) 단계를 분리하여 기록하고 도달 가능성 및 포함성을 검증해야 함을 강조합니다.
핵심 포인트
- 테스트 결과는 '선택/실행/수집' 세 가지 카운트를 모두 확인해야 합니다.
- 패치 적용 시, 실행된 테스트가 선택되었는지(executed ⊆ selected) 반드시 검증해야 합니다.
- 변경 라인에 대한 소유 테스트 도달 가능성(reachability)을 추적하여 정당성을 확보해야 합니다.
42개의 테스트가 통과했다고 보고하는 에이전트 패치는 42개의 테스트가 실행되었고 통과했음을 알려주었을 뿐입니다. 여러분의 전체 테스트 스위트가 통과했다는 것을 알려준 것은 아닙니다. 리포지토리가 900개의 테스트를 수집하고 패치가 그중 42개를 실행했을 때, 나머지 858개는 증거가 아니라 아직 가격을 매기지 않은 가정입니다.
이 게시물은 그러한 범위 주장을 감사 가능하게 만듭니다. 이 아티팩트는 작은 증거 번들(evidence bundle)과 네 가지 게이트로 구성됩니다: 선택/실행 기록부(selection/execution ledger), 변경된 라인에서 소유 테스트까지의 도달 가능성 확인(reachability check), 피처리 읽기 세트 폐쇄 확인(fixture read-set closure check), 그리고 패치가 허용되는 건너뛰기 테스트 집합을 조용히 넓힐 수 없도록 하는 해시 고정 격리 기록부(hash-frozen quarantine ledger)입니다.
모든 패치가 생성하는 세 가지 카운트
테스트 실행은 최소한 세 가지 다른 숫자를 생성하며, 패치는 보통 마지막 숫자만 인용합니다.
| 카운트 | 생성 주체 | 증명할 수 있는 것 |
|---|---|---|
collected | 수집 단계 (collection phase) | 테스트가 여전히 트리에 존재하는지 |
| ... |
이 게이트는 포함성 확인(containment check)입니다: executed ⊆ selected ⊆ collected. 만약 실행된 테스트(executed) 중 선택되지 않은 것이 있다면, 하네스(harness)가 거짓말을 하고 있는 것이며 그 실행 결과에서 나온 어떤 판결도 신뢰할 수 없습니다. 변경된 라인에 executed 내에 소유 테스트가 없다면, 패치는 도달 불가능한 변경 사항(unreachable change)을 가지고 있으며 서면 정당화가 필요합니다.
1단계 — 선택과 실행을 분리하여 기록하기
수집 단계와 호출 단계는 서로 다른 페이로드를 가진 별개의 이벤트이므로, 이를 별도의 파일에 작성해야 합니다. 이것은 참고 플러그인(reference plugin)이며, 의존하기 전에 경로를 조정하고 여러분의 pytest 버전에 대한 훅(hook)을 확인하십시오.
# conftest.py — 증거 기록기 (참고 구현, 경로는 조정하세요)
import json, os, pathlib
...
패치 자체 명령어를 실행한 다음, 통과 카운트를 읽기 전에 포함성을 확인하십시오:
EVIDENCE_DIR=.evidence pytest -q -m "not slow" --junitxml=.evidence/junit.xml
python - <<'PY'
import json, pathlib
...
건너뛴 테스트(Skipped tests)는 executed에 속해야 하지만, 별도의 열에서 카운트되어야 합니다. 실패하는 테스트를 건너뛰는 테스트로 변환하는 패치는 동작(behavior)이 아니라 판결 어휘(verdict vocabulary)를 변경한 것입니다.
2단계 — 변경된 라인을 소유 테스트에 매핑하기
패치의 차이(diff)가 입력이며, 테스트별 커버리지 컨텍스트가 인덱스입니다. 각 테스트 노드마다 컨텍스트를 기록하고, JSON으로 내보낸 다음, 교집합을 구합니다.
coverage run -m pytest -q
coverage json -o .evidence/coverage.json
# reachability.py — 참조 구현체; 먼저 JSON 스키마를 검증하세요.
import json, re, subprocess, sys
...
주의할 점이 두 가지 있습니다. contexts 키는 컨텍스트가 기록되었을 때만 존재하며, 그 형태는 coverage.py 릴리스마다 변경되었습니다. 따라서 실제로 생성되는 JSON을 확인해야 합니다. 또한, 완전히 새로운 파일의 완전히 새로운 라인은 어떠한 컨텍스트도 갖지 않으므로, "새 파일"을 자동 실패로 처리하기보다는 "모듈을 임포트하는 실행 중인 테스트가 최소 하나 필요함"으로 간주해야 합니다.
3단계 — 피처 읽기 집합(Fixture read-set) 폐쇄성 검사
여기서 패치가 실패할 수 있는 방향은 두 가지입니다. 첫째, 테스트를 통과시키기 위해 피처(fixture)를 재작성하는 경우, 둘째, 피처를 재작성했지만 그 피처를 읽는 테스트를 실행하지 않는 경우입니다. 이 두 가지 모두 피처 이름으로 키가 지정된 피처 레지스트리를 유지하면 감지할 수 있습니다.
pytest --fixtures-per-test -q > .evidence/fixture_usage.txt
# .evidence/fixture_readsets.yaml — 당신이 유지 관리하며, 생성되는 것이 아님
# 디스크 상의 입력값이 해시화하기에 충분히 안정적인 피처만 여기에 속합니다.
db_session:
...
폐쇄성 규칙(closure rule): 만약 패치가 피처의 reads 목록에 있는 어떤 파일이라도 건드렸다면, 해당 피처를 선언한 모든 테스트(fixture_usage.txt 기준)는 반드시 executed.jsonl에 나타나야 합니다. 그렇지 않으면 게이트가 차단되며, 패치 작성자는 .evidence/scope_exceptions.yaml에 테스트 이름, 이유, 만료 날짜를 명시하여 항목을 작성해야 합니다. 예외 사항은 조용히 처리되지 않고 검토됩니다.
4단계 — 불안정성 격리(Flaky quarantine) 동결
이것은 가장 가치가 높은 게이트입니다. 왜냐하면 압박감에 놓인 에이전트에게는 저렴한 탈출구가 있기 때문입니다. 즉, 실패하는 테스트를 건너뛰기(skipped)로 표시할 수 있습니다. 격리(Quarantine) 자체는 정당합니다 — 불안정한 테스트는 신호를 파괴하기 때문입니다 — 하지만 이 목록은 보호하는 패치에 의해 수정될 수 없어야 합니다.
.evidence/quarantine.yaml — CI에서 콘텐츠 해시 처리
- nodeid: tests/test_ingest.py::test_retry_budget
reason: 병렬 실행 시 타이밍 의존성(timing-dependent under parallel execution)
...
| 원장(ledger)에 대한 변경 | 정책 |
|---|---|
| 격리된 새 테스트 추가 (Add a new quarantined test) | 차단됨; 사람의 승인 필요 (Blocked; requires human sign-off) |
| ... | |
| Hash 파일을 CI에서 처리하고 기본 브랜치에 저장된 해시와 비교합니다. 추가 사항은 빌드를 실패하게 합니다. 제거 사항은 통과합니다. 이 단일 규칙은 에이전트 패치가 도입한 회귀(regression)를 숨기는 가장 흔한 방법을 제거합니다. |
5단계 — 증거 번들링 (선택적 두 번째 검토자)
위의 모든 내용은 파일을 작성하므로, 번들 작업은 기계적입니다:
| 파일 | 생성자(Producer) | 공급되는 게이트(Gate it feeds) |
|---|---|---|
collected.jsonl, executed.jsonl | 기록기 플러그인 (recorder plugin) | 격리(containment) |
| ... | ||
| 번들이 존재하면, 두 번째 모델은 산문(prose) 대신 JSON 위에서 적대적 검토자(adversarial reader) 역할을 할 수 있습니다. 고정된 질문을 던지세요 — |
도달 가능성(Reachability)은 라인 레벨입니다. 커버리지 영역 내에서 변경된 라인은 여전히 논리적으로 테스트되지 않았을 수 있습니다. 이 게이트는 검색 범위를 좁힐 뿐, 결론을 내리는 것은 아닙니다.
- 선택 감사(selection audit)는 어설션(assertion)이 의미가 있는지, 또는 수집된 인벤토리(inventory)가 줄었는지에 대해서는 아무것도 말해주지 않습니다. 이를 스위트 인벤토리 차이 분석(suite-inventory diffing) 및 반증 가능성 검사(falsifiability checks)와 짝지어 사용하십시오.
- 테스트별 컨텍스트(Per-test contexts)를 추가하면 런타임과 저장 공간을 차지합니다. 대규모 스위트의 경우, 변경된 테스트 파일에 대해서만 컨텍스트를 기록하십시오.
- 전체 스위트가 몇 분 안에 실행되는 경우에는 이 과정을 건너뛰십시오. 그럴 때는 모든 것을 실행하고 선택(selection) 자체를 완전히 건너뛰는 것이 좋습니다. 감사는 선택이 존재하는 곳에서만 비용 대비 효과가 있습니다.
- 피처 레지스트리(fixture registry)의 소유자가 아무도 없는 경우에도 건너뛸 수 있습니다. 오래된
fixture_readsets.yaml파일은 잘못된 이유로 패치를 차단하며, 팀들은 경고를 울리는 게이트를 우회하는 법을 배우게 됩니다.
이번 주에 추가하기 가장 저렴한 게이트는 격리 해시(quarantine hash)입니다. 왜냐하면 커버리지 도구가 필요 없고 CI 단계가 하나만 필요하기 때문입니다. 다음은 포함성 검사(containment check)입니다. 도달 가능성과 피처 폐쇄(fixture closure)는 앞의 두 가지가 지루하고 자동화된 후에 가치가 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기