픽스처를 위한 카나리아 변이: 에이전트가 작성한 패치에 대한 민감도 게이트
요약
테스트가 통과하는 것만으로는 부족하며, 에이전트가 패치를 통해 테스트의 민감도를 약화시킬 수 있습니다. 본문은 '속성 의무(property obligations)'와 '카나리아 변이'를 활용하여, 패치가 여전히 오류를 잡아낼 수 있는지 측정하는 사전 병합 검사 방법을 제시합니다.
핵심 포인트
- 테스트 통과보다 픽스처의 민감도 점검에 초점을 맞춰야 합니다.
- 에이전트는 필드 추가 등으로 테스트를 약화시켜 오류 감지 능력을 떨어뜨릴 수 있습니다.
- '속성 의무(property obligations)'는 한 줄짜리 계약으로 명확한 검증 기준을 제공합니다.
- 의무사항은 반드시 '카나리아 변이'를 포함하여 구체적인 수정 사항이어야 합니다.
테스트 스위트가 녹색(green)이라는 것은 에이전트가 작성한 패치가 가질 수 있는 가장 약한 증거입니다. 대신 픽스처의 민감도에 초점을 맞추세요. 즉, 패치가 건드리는 모든 픽스처는 여전히 무언가를 실패시킬 수 있어야 합니다. 이 글에서는 세 가지 계층 — 속성 의무(property obligations), 카나리아 변이(canary mutations), 그리고 만료일이 있는 동결 레지스트리(freeze registry) — 와 이를 사전 병합 검사(pre-merge check)에 연결하는 코드를 제공합니다.
요약
테스트가 통과하는 이유는 두 가지입니다. 첫째, 코드가 정확하거나, 둘째, 테스트가 아무것도 제약하지 않도록 멈췄기 때문입니다. 에이전트들은 의도치 않게 후자(두 번째 이유)에 매우 능합니다. 그들은 픽스처에 필드를 추가하거나, 단언(assertion)을 완화하거나, 타입을 확장하여 새로운 코드 경로가 맞도록 만듭니다. diff에서 잘못된 것은 아무것도 보이지 않습니다. 스위트는 여전히 녹색입니다.
따라서 "테스트가 통과했는가?"라고 묻는 것을 멈추고, "이 픽스처가 여전히 의도적인 오류를 몇 개 잡아낼 수 있는가?"라고 질문하기 시작하세요. 그 숫자는 측정 가능하고, 계산 비용이 저렴하며, 스위트보다 먼저 움직입니다.
통과하는 스위트가 지연된 지표인 이유
실패 모드는 픽스처 드리프트(fixture drift)입니다. 픽스처는 최소한의, 의견이 담긴 객체로 시작합니다: 세 개의 필드, 하나의 중첩 리스트, 하나의 경계값. 패치가 네 번째 필드를 추가합니다. 그런 다음 누락된 필드에 대한 기본값을 추가합니다. 여섯 번의 패치 후, 픽스처는 모든 코드 경로를 만족시키고 어떤 것도 제약하지 않는 초집합(superset)이 됩니다.
단언(Assertions)도 같은 방식으로 드리프트합니다. assert result.ok는 assert result == Expected(items=[...], total=Decimal("41.10"))보다 약한 계약입니다. 두 번째 단언을 첫 번째 단언으로 교체하는 에이전트는 당신에게 거짓말을 한 것이 아닙니다. 패치를 적용하기 어렵게 만드는 테스트를 단순화했을 뿐입니다.
아래의 게이트는 바로 그러한 부정직함이 없는 실패(dishonesty-free failure)를 위해 설계되었습니다. 이는 악의적인 의도를 감지하려고 시도하지 않습니다. 대신, 테스트 하네스(test harness)가 여전히 물어뜯는지 측정합니다.
계층 1: diff를 검토하기 전에 속성 의무를 작성하세요
속성 의무(property obligation)란 한 줄짜리 계약과 그것이 잡아내야 하는 오류를 의미합니다. 이를 리뷰어의 머릿속에 두지 말고, 패치가 건드려야 하는 파일에 보관하세요.
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
tests/obligations.yaml
- id: invoice-total-is-sum-of-lines
property: "sum(line.amount for line in invoice.lines) == invoice.total"
...
이 파일을 정직하게 유지하는 세 가지 규칙이 있습니다.
- 모든 항목은 존재하는 테스트의 이름을 명시해야 합니다. 오래된
catches:경로는 TODO가 아니라 깨진 의무사항(obligation)입니다. - 모든 항목은 카나리(canary)를 명시해야 합니다. 즉, 테스트를 빨간색으로 만들어야 하는 구체적인 수정 사항이어야 합니다. 만약 그 오류 발생을 한 문장으로 설명할 수 없다면, 해당 property는 너무 모호하여 강제하기 어렵습니다.
- 항목은 diff와 함께 검토되어야 하며, 나중에 검토되어서는 안 됩니다. 패치가
catches:대상을 삭제하거나 이름을 변경하는 경우, 의무사항 파일(obligations file)도 같은 커밋에서 변경되어야 합니다.
이 파일은 에이전트에게 작업을 맡기기에 가장 저렴한 장소이기도 합니다. 에이전트에게 각 새로운 property에 대한 카나리를 제안하도록 요청한 다음, 직접 적용하여 그 카나리를 검증할 수 있습니다.
레이어 2: 각 피처(fixture)가 여전히 카나리(canary)를 포착하는지 증명하기
이것이 측정입니다. 각 피처 파일에 대해 스크래치 복사본의 리포지토리에서 일련의 기계적 변형(mechanical mutations)을 적용하고, 해당 피처에 의존하는 테스트를 실행한 다음, 이 테스트 스위트가 몇 개의 변형을 감지하는지 계산합니다.
# tools/fixture_canary.py — stdlib + pytest only; point the paths at your repo
from __future__ import annotations
import argparse, json, re, shutil, subprocess, sys, tempfile
...
fixture_usage.json 맵은 유일하게 수동으로 처리해야 하는 부분입니다. 테스트 트리에서 피처 경로를 grep을 사용해 한 번 생성한 다음, 직접 검토해야 합니다. 잘못된 매핑은 양방향 모두에서 잘못된 자신감(false confidence)을 초래합니다.
두 가지 모드로 실행하세요:
# first time: record current sensitivity as the floor
python tools/fixture_canary.py --write-baseline
...
이 기준선(baseline)이 바로 지점입니다. 절대적인 민감도(Absolute sensitivity)는 피처마다 노이즈가 심하므로, 흥미로운 신호는 패치가 건드린 피처에서 발생하는 하락세입니다. 만약 어떤 피처가 어설션(assertions)을 수정하는 패치 내에서 3/3에서 1/3으로 떨어진다면 이는 강력한 신호이며, CI에서 한 줄의 비용이 발생합니다.
레이어 3: 소유자(owner)와 만료 날짜로 불안정한 테스트 고정하기
불안정한 테스트(Flaky tests)가 이 게이트를 오염시킵니다. 만약 테스트가 간헐적으로 실패한다면, 모든 카나리 변이가 포착된 것처럼 보이고 민감도는 의미가 없어집니다. 그러니 이를 동결(freeze)시키세요—하지만 조건이 붙은 부채처럼 동결해야 합니다.
# tests/quarantine.yaml
frozen:
- test: tests/net/test_retry.py::test_backoff_timing
...
# tools/check_freezes.py — run on a daily schedule, not on PRs
import datetime, sys, yaml
...
두 가지 제약 조건이 동결을 신뢰성 있게 만듭니다. 첫째, covers_obligations를 나열하는 동결은 해당 픽스처의 민감도 실행에서 제외되어야 합니다. 그렇지 않으면 카나리 게이트가 아무도 실행하지 않는 테스트에 대해 통과(pass)를 보고할 것입니다. 둘째, 만료는 스케줄 상의 치명적인 실패입니다. 작동하는 알람이 없는 동결은 단지 추가 단계가 붙은 삭제일 뿐입니다.
에이전트 루프에 통합하기
패치 검토에 제가 사용하는 워크플로우 순서:
- 에이전트가 패치를 엽니다. 코드를 읽기 전에
tests/obligations.yaml의 diff를 읽습니다. 의무(obligation) 변경 없이 테스트만 변경된 것은 위험 신호입니다. - 패치 브랜치에 대해
fixture_canary.py를 실행하고main의 기준선과 비교합니다. check_freezes.py를 실행합니다. 만료된 동결이 포함되어 있다면 중단합니다—패치는 실행되지 않을 수 있는 테스트에 대해 평가되고 있기 때문입니다.- 그 후에만 코드 diff를 읽습니다.
2단계가 가장 비용이 많이 드는 부분입니다. 왜냐하면 건드린 각 픽스처당 변이(mutation) 수만큼 테스트 실행을 곱하기 때문입니다. 이것은 무료 서버 옵션이 유용한, 지루할 정도로 병렬적이며 CPU 전용 작업입니다—작업 시간이 짧고 GPU가 필요하지 않습니다. 또한 무료 모델 접근이 도움이 되는 지점입니다: 기계적인 것이 아니라 의미론적으로 그럴듯한 픽스처 변이(통화 코드 교체, 부호 반전, 상태 열거형 변경 등)를 제안하도록 모델에 요청하고, 그런 다음 실제로 적용하여 각 제안을 검증합니다. 생성된 카나리 변이는 실행할 때까지 후보입니다.
고지(Disclosure): 이 문서는 MonkeyCode의 제품 홍보 활동의 일환으로 작성되었습니다. MonkeyCode는 무료 모델 접근 및 무료 서버 옵션을 제공하는 오픈 소스 프로젝트입니다. 운영자는 이 두 가지를 광고하며, 특정 토큰 허용량이나 용량은 프로모션 성격이므로 파이프라인을 구축하기 전에 반드시 프로젝트의 현재 약관과 비교하여 확인해야 합니다. 무료 등급(free tier)은 용량 계획이 아니라 워크플로우를 시도해 보는 방법으로 간주하십시오.
결정표 (Decision table)
| 픽스처 민감도 (Fixture sensitivity) | 의무 차이 (Obligation diff) | 동결 상태 (Freeze state) | 조치 (Action) |
|---|---|---|---|
| 기준선 또는 그 이상 (Baseline or better) | 존재함, 모든 목표가 있음 (Present, all targets exist) | 건드리는 것 없음 (None touching) | 일반 검토 후 병합 (Merge after normal review) |
| ... |
제한 사항 및 이 기능을 사용하지 말아야 할 사람
이 게이트는 정확성을 확인하지 않습니다. 이는 귀하의 하네스(harness)가 여전히 반응하는지 여부를 확인합니다. 모든 카나리(canary)를 포착할 수 있는 스위트도 잘못된 동작을 코딩화 할 수 있으며, 3/3 민감도의 픽스처는 아무도 원하지 않는 비즈니스 규칙을 인코딩할 수 있습니다.
뮤테이션 세트(mutation set)는 기계적이고 얕습니다. blank_values는 특정 숫자 경계에만 민감한 픽스처를 포착하지 못하며, 텍스트 수준의 정규 표현식 (regex) 뮤테이션은 바이너리 픽스처 형식에서 성능이 떨어집니다. 만약 귀하의 픽스처가 스키마(schema)로부터 생성된 것이라면, 파일을 수정하는 대신 스키마를 수정하십시오.
비용은 현실적입니다. N개의 픽스처 × 3가지 뮤테이션 × 귀하의 테스트 서브셋(test subset)은 대규모 레포지토리(repo)에 많은 pytest 호출을 발생시킵니다. 모든 픽스처가 아닌, 최근 패치로 가장 많이 건드린 열 개의 픽스처부터 시작하십시오.
다음 경우 건너뛰십시오: 테스트가 12개 미만인 제품 이전 단계(pre-product)라면 — 아직 감지할 픽스처 드리프트(fixture drift)가 없습니다. 스위트 자체가 검토 루프(review loop)보다 느리다면; 먼저 실행 시간(runtime)을 수정하십시오. 단일 픽스처가 전체 테스트 트리에서 공유되는 경우: 카나리 결과는 전체 레포지토리에 대한 하나의 숫자가 될 것이며, 로컬에서는 아무것도 알려주지 못합니다.
만약 러너(runner)를 프로비저닝(provisioning)하지 않고 병렬 변이 스윕(parallel mutation sweep)을 시도하고 싶다면, MonkeyCode의 무료 모델 액세스 및 무료 서버 옵션이 시작하기에 적절한 장소입니다. 10개 피처(fixture) 서브셋과 기준선 파일(baseline file)을 가져와서, 그 게이트가 이미 약하다는 것을 알고 있는 패치에서 작동하는지 확인해 보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기