도메인, 어설션, 피처 래시: 강도에 대한 게이트 에이전트 패치
요약
본 문서는 소프트웨어 테스트의 강도(Strength)를 측정하는 '속성 검사' 시스템에 대한 기술적 분석을 제공합니다. 이 시스템은 도메인, 어설션, 피처 래시 등의 변경이 테스트의 신뢰성을 약화시키는지 확인하며, 개발자가 패치를 병합하기 전 단계별 절차(스냅샷 및 델타 계산)를 상세히 안내합니다.
핵심 포인트
- 속성 검사는 불변 조건 위반을 감지하는 핵심 메커니즘입니다.
- 강도는 주관적 점수가 아닌 재계산되는 필드 값으로 측정됩니다.
- 패치 적용 시, 기본 속성을 제거하거나 경계를 좁히는 행위가 금지됩니다.
- 테스트 강도 검증은 부모 리비전 스냅샷과 패치된 작업트리 스냅샷을 비교하는 방식으로 진행됩니다.
속성 도메인을 축소하거나, 어설션을 제거하거나, 케이스를 교체하거나, 피처 래시(fixture hash)를 대체하는 에이전트 패치는 테스트를 약화시켰습니다. 이러한 변화는 리뷰 실패 사유입니다. 이는 간헐적인 문제가 아니며, 플레이크 동결 대상도 아닙니다.
이 메모는 해당 커트(cut)에 대한 사전 병합 원장(pre-merge ledger)입니다. 기본 속성 카드와 패치 후의 카드를 비교하고, 강도가 잘못 움직일 때는 변경을 거부해야 합니다. 동결 기록은 다운스트림에서 유지됩니다. 그것들은 더 작은 검사를 절대 복구하지 않습니다.
원장이 측정하는 것
속성 검사는 선언된 도메인 내의 입력이 불변 조건(invariant)을 위반할 때 실패합니다. 여기서 강도(Strength)는 주관적인 점수가 아닙니다. 그것은 소스 및 피처 바이트에서 재계산되는 몇 가지 필드입니다.
도메인 크기는 구체적인 케이스 목록의 길이이거나, 리터럴로 작성된 포함 정수 경계값의 곱입니다. 어설션 개수는 속성 함수 내의 assert 문의 개수로, 테스트 프로세스에서 읽는 것이 아니라 ast를 사용하여 읽습니다. 피처 식별자는 해당 속성이 로드하는 피처 파일의 SHA-256 값입니다. 케이스 식별자는 단순히 목록의 길이가 아니라 케이스 키들의 집합입니다.
패치는 케이스를 추가하거나 어설션을 추가할 수 있습니다. 하지만 명명된 포기(waiver) 파일이 존재하는 경우를 제외하고는 기본 케이스를 제거하거나, 리터럴 경계를 좁히거나, assert를 제거해서는 안 됩니다. 피처 해시 변경은 그 자체로 한 종류입니다. 이는 프로덕션 코드 수정과 함께 이동하지 않습니다.
CPython은 -O 옵션으로 시작할 때 assert 문을 실행하지 않습니다. 따라서 최적화된 실행에서 이를 세는 것은 강도를 과소 보고합니다. 이 원장은 리뷰어가 열어볼 수 있는 파일을 읽으며, 해당 모듈의 헬퍼 어설션을 포함하여 속성 함수만 순회합니다.
단계 1. 부모 리비전으로부터 기본 카드 스냅샷
에이전트 패치의 부모를 체크아웃합니다. 속성 ID당 하나의 JSON 카드를 방출합니다. 해당 SHA의 CI 아티팩트와 함께 카드들을 저장합니다. 스냅샷 후에는 수동으로 편집하지 마십시오.
git rev-parse HEAD > .ledger/base_sha.txt
python -m strength_ledger snapshot \
--tests tests/properties
...```
`strength_ledger`는 공개된 패키지가 아니라 제안된 모듈 레이아웃입니다. 명령어들은 인터페이스를 보여줍니다. 이것들이 라이브 저장소의 기록을 옮긴 것이 아니며, 통과율이 주장되는 것도 아닙니다.
## 2단계. 임시 작업트리에 패치를 스냅샷합니다
에이전트 패치는 반드시 분리된(detached) 작업트리 내부에서만 적용해야 합니다. 동일한 스냅샷을 다시 실행합니다. 속성 ID별로 차이를 확인하세요. 포맷팅 노이즈는 무관합니다. ID가 누락되었다는 것은 삭제를 의미하며, 이는 전체 속성을 제거하고 게이트에 실패하게 만듭니다.
git worktree add --detach ../patch-review "$PATCH_SHA"
python -m strength_ledger snapshot
--tests ../patch-review/tests/properties
...
종료 코드 `0`은 공유 속성이 약화되지 않았음을 의미합니다. 종료 코드 `2`는 하락(drop), 케이스 스왑(case swap), 피처 스왑(fixture swap) 또는 제거된 ID를 의미합니다. 종료 코드 `3`은 공유 ID가 유지되었고 패치가 새로운 ID를 추가했음을 의미합니다. 새로운 ID는 별도의 검토 경로가 필요합니다. 이 레지저는 나중에 어떤 프리즈 파일이 언급할지, 혹은 그것을 정의하는지에 대해서는 말하지 않습니다.
## 3단계. 작은 모듈에서 델타를 계산합니다
아래 모듈은 실행되지 않은 예시입니다. 이는 의사 결정 절차(decision procedure)를 문서화한 것입니다. 에이전트, 모델 또는 CI 이력에 대한 측정된 실행 결과를 보고하는 것이 아닙니다.
import ast
import hashlib
import json
...
`domain_size`는 구체적인 케이스 목록이나 포함적 정수 리터럴을 받으며, 둘 다를 받는 경우는 없습니다. 부동 소수점 경계(Float bounds), 샘플링된 생성기(sampled generators) 및 설정 파일에서 로드된 경계는 파싱 오류입니다. 추측한 제품보다 명확한 오류가 더 안전합니다.
길이만으로는 충분하지 않습니다. `cases_weakened`는 에이전트가 새로운 키를 추가하여 길이가 증가하더라도, 기본 케이스 키 중 하나라도 사라지면 패치에 실패합니다. `bounds_weakened`는 명명된 구간(named interval)이 좁아지거나 제거되면 패치에 실패합니다. 추가적인 키와 더 넓은 리터럴 구간은 모든 기본 입력이 여전히 포함되는 경우에만 유지되거나 증가하는 것으로 간주됩니다.
`assertion_count`는 `pytest.raises` 및 사용자 정의 `check_*` 헬퍼를 무시합니다. 그러한 술어(predicates)들은 여전히 중요합니다. 이들은 프로젝트별 방문자(project-specific visitor) 또는 사람의 검토가 필요합니다. 표준 라이브러리 카운트는 최소 기준일 뿐, 전체 오라클은 아닙니다.
## 4단계. 동결 논의 전에 의사 결정 테이블 적용
| 관찰된 델타 | 도메인 (Domain) | 어설션 (Assertions) | 케이스 및 경계 (Cases and bounds) | 피처 해시 (Fixture hash) | 게이트 (Gate) | 동결 파일 (Freeze file) |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| Production edit only | same or up | same or up | base set retained | same | continue | not yet; strength is only the first gate |
| ... |
Waiver는 체크인된 Markdown 노트입니다. 이 노트에는 속성 ID(property id), 이전 카드(old card), 새 카드(new card), 그리고 스펙 링크가 명시됩니다. 이는 타임아웃이 아닙니다. 녹색 체크로 감쇠되지도 않습니다. 해당 파일이 없으면, 종료 코드 `2`가 적용됩니다.
다음 카드 모양을 사용하세요. 0과 빈 컬렉션은 CI 데이터에서 관찰된 것이 아니라 스키마를 보이게 하기 위한 임시 값입니다. 이를 스냅샷 출력으로 대체하십시오.
{
"prop_id": "prop_balance_non_negative",
"domain_size": 0,
...
`sha256sum`을 사용하여 다이제스트를 재계산하고 `fixture_sha256`과 비교하십시오. 만약 다르다면, 카드가 오래된 것입니다(stale). 폐쇄적으로 실패합니다(Fail closed). 오래된 해시를 덮기 위해 동결 목록을 업데이트하지 마십시오.
## 5단계. 자유 모델이 입력값을 제안하게 하고, 예상값은 절대 사용하지 않게 하라
공개 정보: 이 문서는 MonkeyCode의 제품 아웃리치(product outreach)의 일환으로 작성되었습니다.
여기서 사용된 가용성 주장(availability claims)은 이 초안에 제공된 것들, 즉 자유 모델 액세스와 무료 서버 옵션에 한정됩니다. 어떠한 모델 ID, 할당량(quota), 리전, 하드웨어 모양 또는 보존 기간도 가정되지 않았습니다. 이들은 게이트에 포함되어서는 안 됩니다.
자유 모델 액세스는 하나의 임무만을 가지며, 그것도 종료 코드 `0` 이후에만 가능합니다. 이는 카드가 약해지지 않은 속성에 대한 후보 입력값을 제안할 수 있습니다. 로컬 속성이 판결을 계산합니다. 모델 텍스트는 예상값이 아니며, 피처(fixture)에 다시 기록되지 않습니다. 피처를 쓰면 해시가 변경되어 패치를 종료 코드 `2`로 반환하게 됩니다.
```python
def accept_candidate(raw: str, parser, prop) -> str:
try:
value = parser(raw)
...
파싱은 한 번만 수행합니다. 파싱에 실패하면 해당 문자열을 폐기합니다. 적절한 입력이 나타날 때까지 모델을 반복 실행하지 마십시오. 폐기된 제안 하나하나가 유효한 원장(ledger) 결과입니다. 패치가 올바른지 여부에 대해 모델에게 질문하지 마십시오. 그 문장은 카드에 필드가 아닙니다.
6단계. 원장이 통과한 후 고정된 후보 파일 재실행하기 (Replay)
무료 서버는 동일하게 파싱된 후보들을 위한 두 번째 실행 사이트입니다. 이는 선택 사항입니다. strength_delta.json에 strength_ok가 기록된 후에만 시작하십시오. 거부된 패치는 해당 프로세스에 도달하지 않으므로, 원격 가용성은 강도 결정(strength decision)을 변경할 수 없습니다.
재실행 전에 후보 목록을 고정(Pin)하십시오. 파일에 부모 SHA와 속성 ID(property id)를 포함시키십시오. 실행 간에 후보들을 재생성하지 마십시오. 서로 다른 프롬프트로 두 번 실행하는 것은 동일한 실험이 아닙니다.
python -m strength_ledger replay \
--cards .ledger/patch_cards.json \
--candidates .ledger/candidates.json \
...
로컬 속성(local property)에 실패하는 후보는 반례(counterexample)입니다. 이를 델타 JSON 옆에 저장하고, 코드 수정(code fix)을 위해 패치를 다시 보냅니다. 그 경로는 플레이크 동결(flake freeze)을 열지 않습니다. 동결은 나중에 오는 통제 장치이며, 저장된 후보와 연결되지 않고 변경되지 않은 강도 카드 위에 남아있는 실패에 대한 것입니다. 나중에 기록이 인용되거나 만료되거나 되돌려지는 방식은 이 원장 밖에 있습니다.
7단계. 새로운 플랫폼 대신 네 가지 검토 파일 유지하기
검토자들은 짧은 아티팩트 세트를 봐야 합니다.
.ledger/base_cards.json에는 부모 카드들이 들어 있습니다. CI가 이를 재생성합니다. 인간은 이를 편집하지 않습니다..ledger/strength_delta.json에는 패치 SHA에 대한 게이트 출력(gate output)이 들어 있습니다..ledger/candidates.json에는 파싱된 입력값들이 들어가며, 종료 코드0일 때만 그렇습니다.docs/waivers/<prop_id>.md는 선택 사항이며, 카드가 의도적으로 축소되도록 허용될 때 필요합니다.
이 파일들이 일치하지 않으면 스냅샷은 잘못된 것입니다. 다시 실행하십시오. 속성 ID를 동결 파일에 추가하여 불일치를 수정하려고 하지 마십시오.
플레이크 프리즈(flake freeze)는 속성 소스(property source), 고정값(fixture bytes), 후보 목록(candidate list)이 고정된 상태에서 이동하는 실패에 대한 예외입니다. 강도 저하(strength drop)는 그 반대입니다. 소스나 고정값이 변경되었고, 그 변화가 검사를 더 작게 만들거나 기본 케이스를 대체했습니다. 시그니처는 안정적입니다: 카운트, 케이스 세트, 경계값, 그리고 해시 값입니다.
이러한 이유로 순서(Order)는 고정됩니다. 먼저 레저(Ledger)가 오고, 두 번째로 로컬 속성 실행(Local property execution)이 옵니다. 세 번째는 원격 리플레이(Remote replay)이며, 이는 핀된 파일의 중복으로만 가능합니다. 프리즈 검토(Freeze review)는 마지막에 수행되며, 카드가 평평할 때만 가능합니다. 이 메모는 적격성 절단점(eligibility cut)에서 멈춥니다. 프리즈 프로세스, 러너 페어링, 또는 되돌리기 정책은 명시하지 않습니다.
제한 사항 및 이 게이트를 건너뛰어야 하는 팀
레저 카운트 구조가 의미가 아닙니다. 에이전트는 속성 검사 횟수(assertion count)를 보존하면서도 assert balance == expected를 assert isinstance(balance, int)로 대체할 수 있습니다. 게이트를 술어 읽기(predicate reading)와 페어링하십시오. 녹색의 변화량(green delta)이 오라클이 여전히 강력했다는 증거가 될 수는 없습니다.
케이스 키(case keys)가 안정적이지 않으면 동일한 길이도 중복 케이스를 숨길 수 있습니다. 서브셋 검사(subset check)는 키가 입력을 이름 지을 때만 작동하며, 모든 실행이 새로운 레이블을 생성할 때는 그렇지 않습니다. 스위트(suite)에 안정적인 케이스 키가 없으면 스냅샷을 실패시키십시오. 출력 텍스트에서 키를 합성하지 마십시오.
경계값이 런타임에 계산되는 생성적 테스트(Generative tests)는 도메인 크기(domain size)를 산출하지 못할 것입니다. 경계값이 리터럴이 아닐 때 스냅샷을 실패시키십시오. 이전 실행에서 크기를 추정하여 넣지 마십시오. 추정된 크기는 생성기가 변경되는 순간 가짜 기록이 됩니다.
매주 사양 링크 없이 도메인을 축소하는 팀은 이 게이트를 켜서는 안 됩니다. 이는 그들의 정상적인 수정을 차단할 것입니다. 예측 가능한 우회 방법은 축소를 세탁하는 프리즈 파일입니다. 바로 그 우회 방법이 이 절차가 막기 위해 존재하는 실패 원인입니다. 먼저 면제 습관을 고치거나, 아니면 종료 코드(exit code)를 채택하지 마십시오.
스크린샷 차이점(diffs), 산문 스냅샷(prose snapshots), 그리고 일회성 스크립트는 카드에 속성 인벤토리(property inventory)를 가지고 있지 않습니다. 이 모듈은 이를 임의로 만들지 않습니다. 게이트(gate)는 목표가 빨간 빌드(red build)를 무시하는 것일 때도 잘못된 도구입니다. 무시(silencing)는 카운트(counts)를 건너뜁니다. base_cards.json을 SHA 옆에 보관할 수 없는 스위트(Suites)는 일반 검토 상태로 유지되어야 합니다.
무료 모델 출력은 선언된 도메인 밖에 떨어질 수 있습니다. accept_candidate는 이를 폐기해야 합니다. 무한 재생성(Unbounded regeneration)은 이 방법의 일부가 아닙니다. 무료 서버 다운타임은 장애물이 아닙니다. 로컬 리플레이를 기록하고 진행하세요. 원격 프로세스가 차이점(diff)을 승인하기를 기다리지 마십시오. 레지스터는 이미 분류할 수 있습니다. 만약 할당량(quotas)이나 모델 식별자(model identity)가 파이프라인에 중요하다면, CI에 인코딩하기 전에 운영자에게 확인하십시오. 이 문서는 그것들을 나열하지 않습니다.
첫 번째 속성에 대해 실행할 것들
속성 파일 하나와 해시를 재계산할 수 있는 피처(fixture) 하나를 선택하십시오. 기본 카드(base card)를 생성합니다. 하나의 assert 또는 하나의 기본 사례 키(base case key)를 삭제하는 두 번째 카드를 만드십시오. 아래의 계약 검사(contract check)는 제안의 일부입니다. 이것은 모듈이 위의 종료 코드(exit codes)를 구현할 때만 유효합니다.
python -m strength_ledger diff \
--base .ledger/base_cards.json \
--patch .ledger/weakened_cards.json \
...
삭제된 라인을 복원하고, 다시 차이점(diff)을 내며, 어떤 후보 파일도 작성되기 전에 종료 코드 0을 요구하십시오. 만약 그 두 개의 종료 코드가 안정적이라면, 후보 초안 작업은 이미 공개된 무료 모델 접근 방식을 사용할 수 있고, 두 번째 리플레이는 무료 서버를 사용할 수 있습니다. 어느 쪽도 카드를 대체하지는 않습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기