← 홈Setup Tip

Setup Tip

Setup Tip — 생성 파일에는 다시 생성해 보는 검사가 있어야 한다

모델 정의를 바꾼 변경을 머지하면서 그 정의로부터 만들어지는 스키마 파일을 다시 생성하지 않았고, 개발 브랜치가 빨갛게 됐다. 작은 저장소에서 재현해 보니 일반 테스트는 낡은 생성 파일을 알아채지 못했다. CI에서 생성기를 다시 돌리고 git에 바뀐 게 있는지 물으면 잡힌다. 다만 `git diff`는 새로 생긴 파일을 보지 못하고, 실행할 때마다 출력이 달라지는 생성기는 검사를 늘 실패하게 만든다.

무슨 일이 있었나

어제 한 저장소에서 데이터 모델을 바꾸는 변경을 머지했다. 그 저장소의 JSON 스키마는 모델 정의로부터 생성되는데, 나는 머지 전에 생성 파일 검사를 돌리지 않았다. 스키마는 낡은 채로 들어갔고, 개발 브랜치가 빨갛게 되면서 다른 기여자들의 PR까지 막혔다.

재현: 아무도 다시 돌리지 않은 생성기

작은 저장소에 model.txt를 읽어 schema.json을 쓰는 스크립트를 두었다. model.txt에 필드 하나를 추가해 커밋하고, 스크립트는 다시 돌리지 않았다. 저장소 안의 다른 어떤 것도 schema.json을 읽지 않으니 아무것도 실패하지 않았다. 스키마에는 여전히 필드가 셋이 아니라 둘이었다.

검사: 다시 생성하고, git에게 묻는다

깨끗한 체크아웃에서 생성기를 돌리고, 바뀐 것이 있으면 작업이 실패하게 만든다.

  • python3 gen.py && git diff --exit-code -- schema.json

낡은 커밋에서는 빠진 "email" 줄을 보여 주며 종료 코드 1로 끝났다. 다시 생성해 커밋한 뒤에는 0으로 끝난다. 생성기에 자체 검사 모드가 있다면 그걸 쓰면 되고, 이 방식은 저장소 안에 파일을 쓰는 생성기라면 어디에나 통한다.

빈틈 1: git diff는 새 파일을 보지 못한다

git diff는 추적 중인 파일만 비교한다. 생성기가 새 파일 하나를 더 쓰게 바꾸자 git diff --exit-code는 0으로 끝났고, git status --porcelain에는 ?? schema-extra.json이 떴다. 그러니 상태를 확인하거나, 새 파일을 먼저 '추가 예정'으로 표시한 뒤 비교한다.

  • test -z "$(git status --porcelain)"
  • git add -N . && git diff --exit-code

재현에서는 이 두 명령 모두 추적되지 않은 새 파일 때문에 종료 코드 1을 냈다.

빈틈 2: 매번 다른 결과를 내는 생성기

두 번째 생성기는 출력에 현재 시각을 적었다. 커밋된 출력과 새 출력이 실행할 때마다 달라서, 올바른 커밋에서도 검사가 실패했다. 늘 실패하는 검사는 결국 꺼진다. 검사를 넣기 전에 생성 파일에서 시각, 무작위 ID, 순서가 정해지지 않은 출력을 없앤다.

머지가 일어나는 곳에서 돌린다

검사는 작성자 브랜치만이 아니라 실제로 들어갈 커밋, 즉 머지 결과에서 돌린다. 모델은 머지 상대편에서 바뀌었을 수도 있다. 그리고 필수 검사로 지정해서, 낡은 생성 파일이 그 뒤의 모든 사람 브랜치가 아니라 바로 그 머지를 막게 한다.

정리

생성 파일은 마지막으로 누군가 생성기를 돌린 순간만큼만 최신이다. CI에서 다시 생성하고, 새 파일을 포함한 어떤 차이에도 실패하게 하고, 출력을 결정적으로 유지하고, 실제로 머지되는 커밋에서 돌린다.