테스트 주도 OSS 패치: 코드를 수정하기 전에 실패하는 테스트 케이스를 작성하세요
요약
이 가이드는 오픈 소스(OSS) 패치 과정에서 테스트 주도 접근 방식을 사용하여 버그 수정의 효율성을 높이는 방법을 설명합니다. 핵심은 코드를 수정하기 전에, 실패하는 테스트 케이스(fixture)를 먼저 작성하여 버그를 '코드화'하고 이를 두 개의 커밋으로 분리하는 것입니다.
핵심 포인트
- 테스트 주도 패치는 버그 재현을 위한 입력/예상 출력을 고정합니다.
- 패치 과정을 테스트만 포함한 첫 번째 커밋과 수정 사항만 포함한 두 번째 커밋으로 나눕니다.
- 이상적인 테스트 케이스는 결정적, 오프라인, 순서 지정 가능, 최소화되어야 합니다.
- 버그의 예상 출력은 현재 결과가 아닌 명세서나 문서화된 동작을 기반으로 도출해야 합니다.
테스트 주도 OSS 패치: 코드를 수정하기 전에 실패하는 테스트 케이스를 작성하세요
버그 보고서는 여러분의 기계에서 테스트가 실패할 때까지 재현할 수 없습니다. 그 순간 이전에는 단지 이야기일 뿐입니다. 이 워크플로우는 그 이야기를 테스트 케이스(fixture)로, 즉 포착된 실패 시그니처로 변환하고, 두 개의 커밋으로 구성된 패치를 만듭니다. 첫 번째 커밋에는 오직 테스트만 포함되고, 두 번째 커밋에는 오직 수정 사항만 포함됩니다.
이 과정은 작은 파서 버그의 경우 약 20분이 걸립니다. 이는 오픈 소스에서 가장 부족한 자원인 유지 관리자의 주의력을 절약해 줍니다. 또한 검토자(reviewer)가 깨진 동작을 코드로 인코딩된 형태로 보기 때문에 리뷰도 빨라집니다.
'작동하지 않는다'는 것이 버그 보고서가 아닌 이유
오픈 소스 이슈 스레드의 대부분은 세 가지 패턴에 의해 소비됩니다. 신고자는 입력 없이 결과만을 설명합니다. 유지 관리자는 다른 버전에서 재현해 보지만 아무것도 발견하지 못합니다. 패치는 테스트 없이 적용되어, 6개월 후에 버그가 다시 발생합니다.
실패하는 테스트는 이 세 가지를 모두 제거합니다. 하나의 파일에 입력(input), 버전(version), 그리고 예상 출력(expected output)을 고정합니다. 그런 다음 그 테스트가 영구적으로 수정 사항을 보호하며, 반복적인 비용은 0입니다.
테스트 케이스의 계약 (The fixture contract)
사용 가능한 테스트 케이스는 네 가지 속성을 만족해야 합니다. 결정적(deterministic)이어야 하므로 반복 실행해도 동일한 결과를 제공합니다. 오프라인(offline)이어야 하므로 네트워크나 자격 증명이 필요 없습니다. 순서가 지정되어야 하므로 파일 시스템 열거에 의존하지 않습니다. 최소화(minimal)되어야 하므로 하나의 단언문(assertion)이 전체 실패를 설명할 수 있습니다.
만약 후보 테스트 케이스가 이 속성 중 어느 하나라도 깨뜨린다면, 그것은 위장된 불안정한 테스트(flaky test)입니다. 제품 코드를 건드리기 전에 테스트 케이스부터 수정하세요.
1단계: 신고자의 입력을 고정하기
브랜치와 테스트 케이스 디렉토리를 생성합니다. 버그를 여전히 유발하는 가장 작은 입력 파일을 복사합니다.
git switch -c repro/4821-trailing-comma
mkdir -p tests/regression/fixtures/issue_4821
신고자가 제공한 것과 동일하게 원본 입력을 저장하세요. 비밀 정보, 절대 경로, 호스트 이름은 제거합니다. 현재 출력에서 가져온 것이 아니라 문서화된 동작(documented behavior)을 기반으로 예상 출력을 그 옆에 추가하세요.
예상되는 값(expected value)이 중요합니다. 현재 출력을 복사하는 것은 버그를 올바른 동작으로 인코딩하게 만듭니다. 명세서(spec), 문서(docs), 또는 제대로 작동하는 형제 파서(sibling parser)로부터 기대값을 도출하세요.
2단계: 의도적으로 잘못된 동작을 단언(assert)하기
아래 테스트 모듈은 예시입니다. 가져오기 및 이름을 사용자의 패키지에 맞게 조정하세요. 이 글에서는 실행되지 않았습니다.
# tests/regression/test_issue_4821_trailing_comma.py
from pathlib import Path
...
테스트 하나, 단언(assertion) 하나, 피처(fixture) 쌍 하나로 구성하세요. 파일 이름은 이슈 번호 뒤에 붙여 코드와 스레드 간의 연결 고리가 명확하도록 하세요.
실행하고 실패를 예상하세요:
python -m pytest -q tests/regression/test_issue_4821_trailing_comma.py \
2>&1 | tee /tmp/4821.before.txt
만약 이 테스트가 통과한다면, 당신은 버그를 재현하지 못한 것입니다. 멈추세요. 입증되지 않은 이론에 대해 패치를 작성하지 마세요.
3단계: 실패 시그니처 포착하기
단언 라인들을 작은 파일로 추출하세요. 이것이 수정 전(before-image) 이미지가 됩니다.
grep -E '^(E |FAILED|AssertionError)' /tmp/4821.before.txt | head -20
시그니처는 차이가 나는 정확한 필드와 값을 언급해야 합니다.
diff <(sed -n '/^E /p' /tmp/4821.before.txt) \
<(sed -n '/^E /p' /tmp/4821.after.txt)
테스트와 수정 사항을 분리하여 커밋하세요:
git add tests/regression
git commit -m "test: reproduce #4821 with trailing comma fixture"
git add src/
...
두 개의 커밋은 리뷰어가 수정 전에 테스트가 실패하고 후에 통과하는 것을 확인할 수 있게 합니다. 이것이 바로 git 히스토리로 표현되는 전체 논거입니다.
6단계: 모델의 도움을 받는 곳 (모델이 거짓말할 수 없는 곳)
Fixture를 작성하는 것은 반복적인 작업이며, 이 부분이 모델의 도움이 가장 큰 역할을 합니다. 유용한 프롬프트는 보고자가 언급하지 않은 엣지 케이스(edge cases)를 요청합니다: 빈 값, 중복 키, 유니코드 경계, 매우 긴 줄 등. 모델은 또한 긴 이슈 스레드를 후보 입력값으로 요약할 수도 있습니다.
검증은 인간의 영역입니다. 제안된 모든 fixture는 위의 pytest 명령을 실행하고 출력을 읽음으로써 확인됩니다. 모델은 사용자의 실패 시그니처(failure signature)를 관찰할 수 없으므로, 재현 여부를 확인할 수 없습니다.
MonkeyCode는 운영자별로 무료 모델 접근과 무료 서버 옵션을 제공합니다. 이것이 이 루프에서 중요합니다. 왜냐하면 워크플로우가 쉘(shell), git, 그리고 Python만 필요하기 때문입니다. 무료 모델 접근은 후보 엣지 케이스 fixture를 생성하는 비용을 낮춥니다. 무료 서버 옵션은 reproduce-patch-verify 사이클이 원격 머신에서 실행되도록 하여, fixture 세트가 커지는 동안 로컬 체크아웃을 깨끗하게 유지할 수 있게 합니다. 무료 티어 제한 및 서버 약관은 시간이 지남에 따라 변경되므로, 블로그 게시물에 있는 숫자를 신뢰하기보다는 현재 제품 페이지를 확인하세요. 공개 고지: 이 기사는 MonkeyCode의 제품 홍보 활동의 일환으로 작성되었습니다.
결정 테이블: 패치(patch), 요청(ask), 또는 닫기(close)
| 보고 상태 | 조치 | 이유 |
|---|---|---|
| 버전 문자열 없음 또는 입력값 없음 | 둘 다 요청 | 아직 재현할 수 있는 것이 없습니다 |
| ... | ||
| Fixture를 스레드에 게시하세요. 비록 재현이 실패하더라도 말입니다. 다른 플랫폼의 유지 관리자는 하나의 명령으로 이를 실행할 수 있습니다. |
제한 사항 및 이 과정을 건너뛰어도 되는 경우
Fixture가 모든 버그 유형에 적합한 것은 아닙니다. 불안정한 동시성(concurrency) 버그, 하드웨어 의존적 충돌, 성능 저하(performance regressions)의 경우 반복 실행과 벤치마크 하네스(benchmark harnesses)가 필요합니다. 비공개 데이터는 fixture로 커밋할 수 없으므로, 마스킹되거나 합성된 입력이 필요합니다.
보안 수정(Security fixes)은 추가적인 주의를 요합니다. 공개적으로 실패하는 테스트 케이스는 패치된 릴리스가 존재하기 전에 취약점을 설명할 수 있습니다. 이런 경우에는 재현(reproduction) 과정을 비공개로 유지하고, 먼저 관리자들과 공시(disclosure)를 조율해야 합니다.
엄격한 기여 가이드라인을 가진 팀은 이를 따라야 합니다. 일부 프로젝트는 특정 디렉터리에 테스트가 있거나 새로운 fixture 폴더 생성을 금지합니다. 구조를 만들기 전에 CONTRIBUTING.md 파일을 읽으세요.
마무리
fixture를 작성하고, 그것이 실패하는 것을 지켜보고, 가장 작은 표면(surface)을 수정하고, 그것이 통과하는 것을 지켜보세요. 이 순서가 전체 방법론이며, 두 줄짜리 파서 버그부터 프로토콜 변경에 이르기까지 확장됩니다. 만약 독자가 후보 fixture에 대한 제2의 의견을 원한다면, MonkeyCode의 무료 모델 접근은 로컬에서 실행하기 전에 엣지 케이스(edge cases)를 찾기에 합리적인 장소 중 하나입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기