픽스처가 대상에 못 닿는 네 형태: 검사 70개가 전부 초록이던 자리
검사를 넣고 통과를 확인하는 것만으로는 그 검사가 무엇을 보는지 알 수 없다. 대상 코드를 부숴 보니 다섯 건이 그대로 초록이었고, 원인이 넷으로 갈렸다. 설정 없는 픽스처, 실패가 생길 수 없는 주입 지점, 값을 안 지나는 단언, 거꾸로 놓인 키 순서다.
공개 조회 8
TL;DR
- 검사가 만들어 둔 색인을 대상 스크립트가 안 읽고 있었다. 통합 점검 70개가 전부 초록이고 겨눈 코드를 훼손해도 초록이었다
- 원인은 넷이다. 픽스처에
.webnovel/config.json이 없어 대상에 못 닿은 것 2건, 오류를 파일 여는 자리에 넣어 실패가 생길 수 없던 것, 표의 존재만 보고 값을 안 본 것, 픽스처의 키 순서가 거꾸로여서 결함이 안 드러난 것 - 검사를 60개에서 87개로 늘렸다. 새로 넣는 검사마다 대상 코드를 부숴 붉어지는지 확인한 뒤 남긴다
웹소설 집필 도구에 기능 둘을 넣었다. 만들어 둔 결과 파일이 원고보다 뒤처졌는지 대조하는 것, 회차를 합치거나 옮긴 뒤 번호를 재배치하는 것이다. 둘 다 작가의 원고와 색인을 직접 만지므로 독립 검토를 여러 번 돌렸다.
여섯 번째 라운드의 검토가 이렇게 보고했다. 그때까지 반영한 수정 15개를 임시 사본에서 하나씩 부숴 보고 통합 점검을 돌렸는데, 대부분 초록이었다는 것이다. 표에서 세 줄을 옮긴다.
| 죽인 것 | 어느 회수를 되돌리는가 | 결과 |
| freshness 를 항상 FRESH_OK 로 | 이번 판 기능 (가) 전체 | 70/0 초록 |
| 원고 되돌리기 1단계·2단계 각각 삭제 | 2라운드 회수 3 | 70/0 초록 |
| 재배치의 색인 사본 전체 삭제 | 2라운드 회수 5 | 70/0 초록 |
첫 줄은 대조 판단을 하는 함수를 "언제나 최신"으로 못 박은 것이다. 그러면 뒤처진 파일이 전부 최신으로 보인다. 기능을 통째로 없앤 셈인데 검사는 통과를 냈다.
v1.16.0 에서도 죽여 봤는데 통과하는 검사를 만난 적이 있다. 그때 고친 것은 기대 문자열 하나였다. 이번에는 다섯 건이고, 왜 안 닿았는지가 넷으로 갈렸다. 설정 없는 픽스처, 실패가 생길 수 없는 주입 지점, 값을 안 지나는 단언, 거꾸로 놓인 픽스처 키 순서다. 이때 검사는 70개였다. 아래 '개선'의 87은 다 고친 뒤의 값이다.
검사가 대상에 못 닿는 네 가지 형태
넷 다 코드 결함이 아니다. 검사가 도는데 검사 대상을 안 지나가는 것이다.
flowchart TD
A[검사 시작] --> B[픽스처 만들기]
B --> C{설정 파일이 있나}
C -- 없다 --> D[경로를 관례로 추론]
D --> E[검사가 만든 색인을 안 읽는다]
E --> F[단언이 통과]
C -- 있다 --> G[검사가 만든 색인을 읽는다]
G --> H[단언이 실제로 판단]
경로를 못 찾는 픽스처
이 도구는 프로젝트마다 .webnovel/config.json 에서 원고와 색인 경로를 읽는다. 설정이 없으면
관례로 추론한다. ms, index, 원고 같은 이름을 순서대로 찾는 식이다.
검사용 픽스처를 만드는 함수가 그 설정을 안 만들었다. 그래서 검사가 index-amb 라는 이름으로
색인을 만들어 두고 스크립트를 돌리면, 스크립트는 그 디렉토리를 아예 안 본다. 다른 자리를 보고
"바꿀 것이 없다" 를 낸다. 단언은 그 출력에서 문자열 하나를 찾는 것이었다. 그 문자열은 막힘
안내에도 들어 있었다.
같은 형태가 둘이었다. 색인 레코드가 모호할 때 막는지 보는 검사와, 실패가 임시 파일을 남기는지 보는 검사다.
설정이 없으면 경로를 관례로 추론하는 도구는 흔하다. 픽스처가 그 설정을 안 만든 순간 시험 대상은 픽스처가 놓아둔 자리가 아니라 다른 디렉토리를 본다.
파일 여는 자리에 넣은 오류
"실패해도 임시 파일을 안 남긴다" 를 확인하려고 쓰기 실패를 넣었다. 파일을 여는 호출에 예외를 던지게 했다.
그러면 파일이 아예 안 만들어진다. 남을 것이 없으니 단언은 언제나 참이다. 갈아 끼우는 호출로 옮기고 나서야 임시 파일이 남았다. 그것을 치우는 코드를 지웠을 때 붉어졌다.
실패 주입은 실패가 실제로 생길 수 있는 지점에 넣어야 한다. 파일을 여는 자리에 던지면 파일이 안 생기므로 '남지 않는다' 가 언제나 참이 된다.
값을 안 지나는 단언
밀린 칸수가 구간마다 다를 때 그 경계를 표로 낸다. 단언이 표의 제목과 안내 문장만 봐서, 계산식을 한 글자 어긋내도 표는 그대로 나오고 값만 틀렸다. 같은 형태를 v1.16.0 에서도 한 번 봤다. 그때는 기대 문자열이 표 위 설명 문장에 들어 있었다.
거꾸로 놓인 픽스처 키 순서
회차를 지우면서 다른 회차의 이름을 그 번호로 바꾸면, 원장의 지문 두 개가 같은 키를 놓고
만난다. 지운 8화의 지문과 7화에서 이름이 바뀐 지문이 둘 다 8화.md 를 가리키는 식이다.
둘 중 하나만 남고, 나중에 처리되는 쪽이 앞엣것을 덮는다. 어느 쪽이 나중인지는 원장 JSON 의
키 순서가 정했다. 그게 결함의 본체였다. 지운 쪽이 남으면 한 글자도 안 고친 회차에
"내용이 바뀌었다" 가 뜨고 전량 재스캔 처방이 나간다.
픽스처에 지운 쪽을 먼저 적었다. 그 순서에서는 이름이 바뀐 쪽이 나중에 처리되므로, 판단 코드를 지워도 맞는 값이 우연히 살아남는다. 순서를 바꾸고 나서야 검사가 붉어졌다.
픽스처 데이터의 키 순서가 판단을 가르는 자리라면, 그 순서를 픽스처가 고정해야 한다. 결함이 드러나는 순서를 골라 적고 왜 그 순서인지 주석에 남겼다.
확인 방법
새 검사를 넣을 때마다 임시 사본에서 대상 코드를 한 줄 부수고 점검을 돌렸다.
SB="$(mktemp -d)/sb"; mkdir -p "$SB"
cp -r webnovel-toolkit "$SB/"; cd "$SB"
R=webnovel-toolkit/skills/webnovel/scripts/renumber.py
cp "$R" /tmp/r.bak
python -c "
import io; p='$R'; s=io.open(p,encoding='utf-8',newline='').read()
s=s.replace(' if not pre:',' if True:',1)
io.open(p,'w',encoding='utf-8',newline='').write(s)"
python webnovel-toolkit/skills/webnovel/scripts/pipeline_check.py \
--skills-dir webnovel-toolkit/skills | grep -E '^\*\*통과'
마지막 줄의 출력이 이렇게 갈린다.
기준선: **통과 87 / 실패 0 / 건너뜀 0**
되돌리기 앞가드 죽임: **통과 86 / 실패 1 / 건너뜀 0**
복원: **통과 87 / 실패 0 / 건너뜀 0**
가운데 줄이 기준선과 같으면 그 검사는 대상에 못 닿은 것이다. 이 절차로 다섯 건을 잡았다. 검사 함수를 직접 부르지 않고 점검을 통째로 돌리는 것이 중요하다. 함수만 부르면 그 검사가 실제로 배선돼 있는지는 아무도 안 본다.
고친 것
검사를 매번 같은 상태의 작은 프로젝트에서 돌리게 바꿨다. 여러 검사가 공유하는 픽스처는 앞선 검사가 상태를 바꿔 놓아서 무엇을 보고 있는지 알기 어렵다.
def make_probe_project(base, eps=6, part=None):
root = os.path.join(base, 'proj')
ms = os.path.join(root, 'ms', part) if part else os.path.join(root, 'ms')
os.makedirs(ms, exist_ok=True)
for e in range(1, eps + 1):
io.open(os.path.join(ms, f'{e}화.md'), 'w', encoding='utf-8',
newline='').write(f'{e}화 본문이다.\n"대사가 있다."\n')
write_probe_config(root, '.webnovel/index')
return root
설정 쓰기를 조건 없이 한다. 처음에는 if os.path.isfile(cfgp2) 로 감싸 두었는데, 그 픽스처에
config.json 이 없으니 조건이 거짓이고 설정은 안 만들어졌다.
표를 단언할 때는 제목이 아니라 행 전체를 기대 문자열로 넣는다. | 12화 | 11화 | -1 | 처럼
값이 들어간 줄을 그대로 적으면 계산식을 어긋냈을 때 붉어진다.
개선
| 전 | 후 | |
|---|---|---|
| 통합 점검 검사 수 | 60 | 87 |
| 이 판에 새로 붙인 검사 중 부숴도 초록이던 것 | 5 | 0 |
남은 것
검사 없이 남은 수정이 여덟 자리다. 검토가 부숴 본 15개 중 13개가 그대로 초록이었고, 검사를 늘린 뒤에도 여덟이 남았다. 그중 둘은 검토가 가장 무겁게 매긴 것이다. 점유를 원고만으로 좁히는 것과, 번호를 뗄 때 색인과 원장의 점유를 보는 것이다. 둘 다 벌어지면 작가의 색인 레코드가 사라진다. 나머지 여섯은 판단과 화면에 닿는 것들이라 덜 무겁다.
한 판에 여덟을 다 넣으면 그중 몇이 다시 대상에 못 닿는 상태로 들어간다. 이번에 그것을 다섯 번 봤으므로 해당 파일을 다음에 건드릴 때 함께 넣기로 했다.
다음부터
새 검사를 넣을 때는 대상 코드를 한 줄 부순 사본에서 전체 점검을 돌려 통과 수가 기준선보다 줄어드는지 보고, 안 줄면 그 검사를 대상에 못 닿은 것으로 보고 고친다. 픽스처에는 도구가 경로를 관례로 추론하지 않도록 설정 파일까지 만들고, 실패는 실패가 실제로 생길 수 있는 호출에 넣는다.