설계 문서를 테스트로 강제하기, spec.yaml과 spec-check

2026. 9. 16. 13:00·IT
728x90
반응형

설계 문서는 쓰는 날이 가장 정확하고 그 뒤로 매일 틀려간다. 코드는 바뀌는데 문서는 안 바뀌기 때문이다. 이 글은 개인 프로젝트의 설계 문서를 "읽는 문서"에서 "실패하는 테스트"로 바꾼 방법을 다룬다. 규격을 YAML 한 파일에 두고, 코드가 그 규격에서 벗어나면 명령 하나가 exit 1을 내게 했다. 문서를 성실하게 갱신하겠다는 다짐보다, 갱신하지 않으면 커밋할 수 없는 구조가 오래간다.

문서와 코드가 어긋나면 어긋난 쪽을 알 수 없다

자동화 파이프라인이 새벽에 조용히 실패했을 때 가장 먼저 여는 것은 설계 문서다. 어느 단계가 무엇을 받아 무엇을 내는지, 어떤 규칙이 발행을 막는지. 그런데 문서에 적힌 검수 규칙이 아홉 개이고 코드에는 열두 개라면, 문서를 읽어서는 원인을 못 찾는다. 코드를 읽어야 하고, 그러면 문서는 애초에 왜 있었나.

흔한 대응은 두 가지다. 문서를 열심히 갱신하기로 다짐하거나, 문서를 포기하고 코드가 곧 문서라고 선언하거나. 첫째는 오래 못 간다. 둘째는 "왜 이렇게 만들었나"가 코드에 남지 않는다. Michael Nygard가 아키텍처 결정 기록(ADR)을 제안하며 든 이유가 정확히 이것이다. "프로젝트의 생애에서 가장 추적하기 어려운 것 중 하나는 어떤 결정의 동기다."

규격을 YAML 로 옮기고 검사기를 붙였다

접근은 단순하다. 문서 가운데 사실관계 표에 해당하는 부분, 즉 CLI 명령 목록, 파일 스키마, 셀렉터 이름, 검수 규칙 ID 같은 것을 docs/spec.yaml 한 파일로 모은다. 산문은 그대로 마크다운에 둔다. 그리고 bgp spec-check라는 명령이 코드의 실물과 YAML을 대조한다.

# docs/spec.yaml (일부)
gate:
  rules:
    - id: title_present
      severity: error
      summary: 제목이 비어 있으면 안 된다.
    - id: body_length
      severity: error
      summary: 본문이 min_chars 미만이면 실패.
cadence:
  hard_cap_per_day: 3
# bgp/spec.py (일부)
def check_spec() -> list[str]:
    spec = load_spec()
    problems = []
    problems += _diff("검수 규칙",
                      [r["id"] for r in spec["gate"]["rules"]],
                      gate.RULE_IDS)
    for key, value in cadence.DEFAULTS.items():
        if spec["cadence"].get(key) != value:
            problems.append(f"cadence.{key}: 문서={spec['cadence'].get(key)} 코드={value}")
    return problems

코드 쪽은 규칙 ID를 리스트 상수로, 기본값을 딕셔너리 상수로 노출하기만 하면 된다. 검사기는 양쪽 집합의 차이를 출력한다. "문서에는 있는데 코드에 없음", "코드에는 있는데 문서에 없음".

이게 실제로 잡는지 확인하려고 코드에만 규칙 하나를 몰래 추가해 봤다.

불일치 검수 규칙: 코드에는 있는데 spec.yaml 에 없음 → ['ghost_rule']
불일치 발행 빈도: cadence.hard_cap_per_day 가 spec.yaml=3 / 코드=9 로 다름
spec-check 실패 — 코드 또는 docs/spec.yaml 을 맞추세요.   exit=1

문서의 표는 YAML 에서 생성하고, 낡으면 그것도 실패다

사람이 읽는 마크다운 문서에는 같은 정보가 표로 들어가야 한다. 그 표를 손으로 쓰면 다시 어긋난다. 그래서 문서 안에 <!-- spec:begin:gate --> 와 <!-- spec:end:gate --> 표식을 두고, bgp spec-docs가 그 사이를 YAML로부터 다시 쓴다.

여기서 한 걸음 더 갔다. spec-check는 "문서의 자동 생성 구간이 지금 YAML로 만들 결과와 같은가"도 검사한다. 다르면 실패다. 즉 YAML을 고치고 spec-docs를 안 돌리면 커밋이 막힌다. 문서 갱신을 잊을 수가 없다.

작업 순서도 이 구조가 정한다. 규격을 먼저 고치고, 코드를 맞추고, 문서를 생성하고, 검사를 통과시킨다. Write the Docs가 "Docs as Code"로 부르는 관행, 곧 문서를 버전 관리·코드 리뷰·자동 테스트의 대상으로 두는 것과 같은 방향이다. 다만 여기서는 테스트가 문서의 형식이 아니라 문서와 코드의 일치를 본다.

검사 없는 규격은 규격이 아니다

이 방식의 비용은 분명하다. 규격 항목을 하나 늘릴 때마다 대응하는 검사 코드도 늘려야 한다. 검사 없이 YAML에만 적힌 항목은 곧바로 낡는다. 그래서 규칙을 하나 정했다. 검사 없는 규격은 추가하지 않는다. 검사를 쓰기 귀찮은 항목이라면 그 항목은 규격이 아니라 산문으로 문서에 남긴다.

반론도 있다. 개인 프로젝트에 이 정도 장치는 과하다는 것이다. 처음 이틀 동안은 나도 그렇게 느꼈다. 생각이 바뀐 것은 셋째 날, 다른 세션이 올린 커밋과 내 커밋이 충돌했을 때다. 양쪽이 spec.yaml의 다른 부분을 늘렸고, 병합 후 spec-check가 즉시 어느 문서 표가 낡았는지 짚어줬다. 사람 둘이 아니라 세션 둘이 번갈아 작업하는 프로젝트에서, 문서가 코드를 따라오게 만드는 장치는 사치가 아니라 전제 조건이었다.

문서를 잘 쓰겠다는 다짐은 사람에게 거는 것이다. 문서가 틀리면 빌드가 깨지게 하는 것은 구조에 거는 것이다. 오래가는 쪽은 언제나 후자다.

참고 자료

  • Documenting Architecture Decisions — Michael Nygard — ADR 의 형식과 "결정의 동기가 사라진다"는 문제 제기
  • Docs as Code — Write the Docs — 문서를 코드와 같은 도구로 다루는 관행
  • Playwright Python — Auto-waiting — 이 프로젝트에서 규격화한 셀렉터 계약이 전제하는 동작
728x90
반응형

'IT' 카테고리의 다른 글

AI 에이전트 보안, 권한을 어디까지 줄 것인가  (0) 2026.09.16
Playwright 고정 대기 대신 상태 대기를 써야 하는 이유  (0) 2026.09.15
오픈소스 라이선스 변경, 기업이 자꾸 바꾸는 이유와 개발자의 대응  (0) 2026.09.15
Playwright intercepts pointer events 해결  (0) 2026.09.14
AI 반도체 거품 논쟁, 매출 1조 달러와 캡엑스 6천억 달러 사이  (0) 2026.09.14
'IT' 카테고리의 다른 글
  • AI 에이전트 보안, 권한을 어디까지 줄 것인가
  • Playwright 고정 대기 대신 상태 대기를 써야 하는 이유
  • 오픈소스 라이선스 변경, 기업이 자꾸 바꾸는 이유와 개발자의 대응
  • Playwright intercepts pointer events 해결
밍글링글링
밍글링글링
mingling - 밍글링, 밍글밍글링. 코드와 어우러지다. IT/ 프로그래밍/소스
    반응형
    250x250
  • 밍글링글링
    mingling
    밍글링글링
  • 전체
    오늘
    어제
    • 밍글링글링 (415) N
      • Flutter (2)
      • 일상생활 (8)
        • 리뷰 (1)
        • 생활정보 (4)
        • 맛집 (0)
        • 여행 (0)
        • 모든정보 (3)
      • JAVA (126)
        • 개념 (6)
        • 예제 (115)
        • Exception (2)
      • C (1)
        • C (1)
        • C++ (0)
        • C# (0)
      • JS (29)
        • JavaScript (18)
        • JQuery (5)
        • AJax (0)
        • NODE.JS (6)
        • Angular.JS 2.0 (0)
      • WEB (87)
        • HTML (6)
        • CSS (61)
        • JSP (20)
        • JSTL (0)
      • FrameWork (8)
        • Spring (8)
        • BootStrap (0)
        • MyBATIS (0)
        • JUnit (0)
      • 외부 라이브러리 (5)
      • 공유 소스 관리 (5)
        • Git (5)
        • SVN (0)
      • 빅데이터 프로그래밍 (37)
        • Python (37)
        • R Programming (0)
      • DB (7)
        • ORACLE (0)
        • MySql (6)
      • Development Tools (7)
        • StarUML (0)
        • eXERD (0)
        • Eclipse (4)
      • SKILL (6)
        • Migration (0)
        • Security (6)
      • MicroSoft (0)
        • Excel (0)
        • Word (0)
      • Android (0)
      • Server (21)
        • Ubuntu (5)
        • Linux (15)
      • IOS (0)
      • XML (0)
      • 미디어 (0)
      • 공지사항 (3)
      • NETWORK (1)
      • 게임 (4)
        • 피파 (1)
        • 리니지M (0)
        • 배틀그라운드 (1)
        • 듀랑고 (2)
      • 세상 이슈 (6)
      • 일렉트론 (0)
      • 대회 소식 (4)
      • 업무 (2)
      • Express, Vue (6)
      • docker (11)
      • svelte (3)
      • 블록체인 (1)
      • IT (18) N
      • Rust (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    servlet class
    vue cli
    nginx
    mysql db
    css float
    자바 배열
    nginx ssl 설정
    css block
    오류
    jsp include
    rust linux
    vue 설치
    API 설계
    Java Array
    자바 exception
    css perspective
    css tb
    proxy pass
    vscode
    lang rust
    에디터 팁
    리눅스 설치
    ssl 인증서 발급
    Rust lang
    java casting
    자바 객체 지향
    러스트
    css table
    svelte
    ubuntu
    자바 생성자
    티스토리 자동화
    프런트엔드
    SSL 인증서
    Extension Bisect
    jsp parameter
    AI 코딩 에이전트
    자바 for문
    spring java
    docker
    css transition
    브라우저 자동화
    Node
    React Compiler
    gitlab 설치
    자바 클래스
    VS Code 팁
    css list
    nginx ssl 적용
    클론코딩
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
밍글링글링
설계 문서를 테스트로 강제하기, spec.yaml과 spec-check
상단으로

티스토리툴바