PokeClip 미디어 CI에서 테스트 파일 하나를 의도적으로 제외했습니다. index의 실행된 문장은 72/88문(81.8%)에서 46/88문(52.3%)으로 줄었지만, 남은 9개 패키지 테스트는 모두 성공했습니다. 테스트 명령의 초록색만으로는 빠진 검증을 알아차릴 수 없었습니다.

저는 핵심 3개 패키지인 index, upload, indexer에 각각 80% 기준을 두고, 통과 실행만 확인하는 대신 테스트 제외→게이트 실패→테스트 복원→다시 통과하는 대조를 요구했습니다. PR #77의 구현·실측·검토는 AI와 협업했습니다. 제가 정한 검증 기준은 측정값을 표시하는 데서 끝나지 않고, 정한 선 아래로 내려가면 CI가 실제로 실패해야 한다는 것이었습니다.

표시용 백분율을 판정에 쓰지 않았다

Go의 커버리지 요약은 소수점 한 자리로 표시됩니다. 실제 값이 79.99%여도 화면에는 80.0%로 보일 수 있습니다. 이 표시값을 읽어 “80.0 이상”이라고 판단하면 80% 미만을 허용하게 됩니다.

그래서 cover.out의 원시 프로파일에서 실행된 문장 수와 전체 문장 수를 합산했습니다. 판정식은 covered × 100 < total × 80입니다. 표시 문자열이 아니라 정수쌍으로 비교하므로 반올림 때문에 경계가 움직이지 않습니다. 정확히 80%인 입력은 통과시키고, 79.9%와 79.99%인 입력은 거부해야 합니다.

집계에서도 두 조건이 중요했습니다. 첫째, -race와 함께 사용하는 atomic 프로파일은 실행 횟수가 1보다 클 수 있어 1과 같은지를 검사하면 안 됩니다. 0보다 큰 블록이 실행된 블록입니다. 둘째, index라는 문자열을 느슨하게 찾으면 indexer까지 섞일 수 있습니다. 패키지 경로를 정확한 키로 나눠 집계했습니다.

실제 Actions에서 실패를 만들고 되돌렸다

게이트가 통과하는 실행만 보면 실제 차단이 동작하는지 알 수 없습니다. 2026년 8월 18일에는 테스트 파일 맨 위에 빌드 제외 표식과 빈 줄, 총 2줄만 추가해 해당 테스트를 실행 대상에서 뺐습니다. 남은 테스트는 모두 성공했지만 커버리지가 떨어져야 하는 대조군입니다.

실제 CI 실행 index 커버리지 9개 패키지 테스트 커버리지 게이트
정상 72/88문 · 81.8% 성공 통과
테스트 파일 하나 제외 46/88문 · 52.3% 성공 실패
제외 표식 복원 72/88문 · 81.8% 성공 통과

정상 실행, 실패 대조 실행, 복원 실행을 따로 남겼습니다. 제외 실행에서 upload 95.1%, indexer 90.2%는 유지됐습니다. 변경이 테스트 상단 2줄이고 다른 게이트 대상은 그대로였다는 점까지 대조해, 빌드 실패나 다른 패키지의 변화 때문에 CI가 빨개진 것이 아님을 확인했습니다.

이 대조에서 달라진 신호는 테스트 성공 여부가 아니라 커버리지 게이트의 종료 결과입니다. 테스트 제외 표식을 되돌리자 81.8%와 통과가 함께 복원됐고, 최종 변경에도 제외 표식을 남기지 않았습니다. 따라서 52.3%→81.8%는 제품 커버리지 개선치가 아니라 검증 장치가 실패를 감지하고 복원까지 확인한 기록입니다.

테스트의 실패를 게이트 성공이 덮지 않게 했다

테스트와 커버리지 판정은 다른 스텝으로 나눴습니다. 테스트가 실패해도 프로파일 파일이 남을 수 있으므로 두 명령을 잘못 합치면 뒤의 성공 종료 코드가 앞의 실패를 가릴 수 있습니다. 테스트 스텝이 실패하면 게이트가 그 실패를 덮지 못하도록 순서를 정한 것입니다.

게이트 대상 패키지가 프로파일에 없거나, 헤더만 있거나, 파일 자체가 없는 경우도 성공으로 처리하지 않도록 했습니다. “측정할 자료가 없다”는 상황을 0개의 실패로 해석하면 핵심 테스트가 통째로 빠졌을 때 검증 장치가 무력해집니다.

2026년 10월 6일에는 원본 awk 게이트에 합성 프로파일 10종을 넣어 판정과 종료 코드를 다시 확인했습니다. 당시 원시 cover.out을 확보하지 못했으므로, 이는 현재 제품의 커버리지를 다시 측정한 실험이 아닙니다.

합성 입력에서 확인한 경계 기대와 결과
정확히 80% 통과
79.9%·79.99% 실패
index·indexer 접두사 각각 독립 집계
atomic 실행 횟수 2 이상 실행 문장으로 집계
필수 패키지 부재·헤더만 존재·파일 부재 성공으로 처리하지 않음
총 10종의 판정·종료 코드 기대와 일치

실제 Actions 대조는 CI 배선까지 포함해 실패가 전달되는지를, 합성 10종은 판정식이 경계값과 누락을 올바르게 처리하는지를 확인합니다. 두 결과가 있어야 “게이트 스크립트는 맞지만 실행되지 않는 경우”와 “실행되지만 반올림된 값을 잘못 허용하는 경우”를 각각 점검할 수 있습니다.

커버리지 기준이 보장하지 않는 것

80%는 코드 품질이나 테스트의 정확성을 완전히 설명하지 않습니다. 실행된 줄에 좋은 검증문이 있는지까지 알려주지도 않습니다. 이 게이트의 역할은 핵심 패키지의 실행 범위가 정한 선 아래로 떨어지는 일을 감지하는 것입니다. 다른 6개 패키지는 당시 측정값을 남기되 게이트 대상으로 삼지 않았습니다.

또한 CI 잡 실패와 브랜치 보호에 의한 강제 병합 차단은 별개입니다. 이후 PR #163의 실행 범위 변경에서는 검사 내용을 유지하면서 필수 체크 정책을 바꿨습니다. 초록색 하나로 모든 보장을 묶지 않고, 테스트 성공·커버리지 판정·실행 대상·병합 정책을 각각 확인해야 합니다.

처음의 초록색 테스트 결과는 틀린 정보가 아니었습니다. 실행한 테스트가 성공했다는 질문에만 답했을 뿐입니다. 저는 그 옆에 핵심 코드의 실행 범위가 유지되는지 묻는 게이트를 두고, 실제 테스트를 빼서 응답이 달라지는지 확인했습니다. 품질 검사는 통과하는 방법뿐 아니라, 어떤 입력에서 반드시 실패해야 하는지도 검증해야 했습니다.


이 글은 PokeClip·MediaMTX 기술 기록 모음의 일부입니다. 포트폴리오 전체본 35쪽에서 연결된 사례도 함께 볼 수 있습니다.