- src/dist 산출물 분리 원칙 정리(.gitignore, .gitattributes) - 루트 및 주요 폴더(config/scripts/prompts/tests/src, 런타임 폴더 5종)에 안내용 README.md 추가 - CHANGELOG.md, LICENSE, docs/ops/05-release-and-versioning.md 추가 - docs/README.md 문서 지도 갱신
22 KiB
DMF Crawler TDD · E2E RED · 디자인 감사 운영 체계
이 문서는 DMF Crawler 의 TDD/RED 운영 SSOT 다.
이론 근거는docs/research/11-tdd-red-and-design-audit-theory.md를 따른다.
상태: v1 확정
적용 시점: 이 문서가 생성된 뒤의 모든 코드·GUI·리포트·에이전트 작업
핵심 명령: 테스트 없이 새 코드를 쓰지 않는다. RED 실패를 먼저 본다.
1. 법칙
1.1 신성한 루프
모든 구현 작업은 다음 순서를 따른다.
- RED — 실패하는 자동화 테스트를 먼저 만든다.
- RED 확인 — 그 테스트가 실제로 실패하며, 실패 이유가 기대한 요구 위반인지 확인한다.
- GREEN — 통과에 필요한 최소 구현만 한다.
- REFACTOR — 전체 관련 테스트가 Green 인 상태에서만 구조·중복·이름·디자인을 정리한다.
- REGRESSION — 영향 테스트 + 전체 smoke 를 돌리고 결과를 기록한다.
1.2 금지
- 테스트가 없는
src/기능 구현 금지. - 실패를 보지 않은 테스트를 “RED” 라고 부르기 금지.
- 테스트를 약하게 바꿔 Green 만들기 금지.
pytest.skip,xfail, 느슨한except Exception: pass,assert True류로 실패 숨기기 금지.- 실패 중에 리팩터링 금지.
- 의미 없는 테스트를 무한히 추가하기 금지.
- 디자인 감사를 “눈으로 대충 봄”으로 대체 금지.
1.3 예외
다음은 테스트 선행이 어려울 수 있다. 그래도 문서화가 필요하다.
| 예외 | 허용 조건 | 대체 증거 |
|---|---|---|
| 문서만 수정 | 코드 동작이 변하지 않음 | 링크/목차/SSOT 일관성 검사 또는 변경 요약 |
| 설치 스크립트/작업 스케줄러 | 실제 Windows 권한이 필요 | dry-run/PowerShell parser/ASCII/BOM 검사 + 수동 프로브 로그 |
| 실제 공공 사이트 접근 | 사용자 승인 필요 | fixture/mock 서버 E2E + 수동 실행 체크리스트 |
| 순수 스타일 문구 | 자동화 어려움 | 디자인 RED 체크리스트와 스크린샷/수기 판정 기록 |
예외를 남길 때는 “왜 자동화 못 했는가 / 어떤 수동 증거로 대체했는가 / 나중에 자동화할 조건”을 적는다.
2. RED 계층 구조
R0 — 계약/부팅 RED
목표: 프로젝트가 import/CLI/패키지 수준에서 깨지지 않음을 보장한다.
필수 검사:
.\.venv\Scripts\python.exe -m compileall -q src tests
.\.venv\Scripts\python.exe - <<'PY'
import importlib, pathlib
for p in pathlib.Path('src/dmf_crawler').rglob('*.py'):
if p.name == '__init__.py':
continue
importlib.import_module('.'.join(p.with_suffix('').relative_to('src').parts))
PY
.\.venv\Scripts\python.exe -m dmf_crawler --help
.\.venv\Scripts\python.exe -m dmf_crawler doctor --json
RED 예시:
cli.py가 없으면python -m dmf_crawler --help실패.storage/repo.py가 없으면pipeline.run_once()런타임 실패..ps1에 BOM 이 없으면 PowerShell 5.1 한글 깨짐 위험.
R1 — 도메인 단위 RED
대상:
normalize.pydiff.pyintegrity.pysource_mfds.py파서agy/extract.pyJSON 추출
원칙:
- 네트워크 없음.
- DB 없음.
- clock/random 고정.
- fixture 로 실패 재현.
대표 RED:
| 위험 | 테스트 |
|---|---|
| 등록번호 파서 회귀 | 표준/신물질/허여서/깨진 입력 10종 |
| openpyxl 함정 재발 | nedrug xlsx 는 sheet XML 직접 파싱 요구 문서/테스트 |
| API 오류 봉투 2종 | JSON 정상 봉투 + XML/JSON Gateway 오류 봉투 |
| 조용한 빈 파일/빈 응답 | 200 OK + 헤더만/0건은 무결성 게이트 차단 |
| 발급일자 변경 신호 | permit_date 변경 시 CHANGED |
| 취하 오탐 | totalCount 급감/전건 취하율 임계 초과 시 diff 폐기 |
R2 — 저장소/통합 RED
대상:
storage/db.pystorage/repo.py- migrations
pipeline.py
필수 RED:
- 새 DB 에 migrations 0001~0003 적용.
- 중복 적용 시 checksum 검증 후 무변화.
start_run→ checkpoint → snapshot → events → report data 조회.- 같은 날짜 SUCCESS 중복 방지.
- 기준선 첫 실행은 이벤트 0건.
- 스냅샷 저장 전 무결성 실패면 DB 오염 없음.
R3 — 파이프라인 E2E RED
목표: 사람이 실제로 쓰는 흐름을 fixture 로 끝까지 검증한다.
시나리오:
- 첫 설치/키 없음
doctor가 API 키 없음 WARN 을 낸다. 인증키는 선택 기능이다.source.mode=auto는 API 키가 없으면 AGY headful Chrome CCBAC03 xlsx 수집을 시도한다.source.mode=api에서 키가 없거나 headful 수집이 실패하면 BLOCKED 가 아니라 마지막 성공 자료 PARTIAL 리포트로 폴백한다.
- 첫 수집 기준선
- fixture API 3건 → DB snapshot 3건.
- diff 이벤트 0건.
- 대시보드 배너 “기준선 수립일”.
- 둘째 날 변경
- 신규 1 / 변경 1 / 취하 1 fixture.
- events 3건, report changes 3행.
- 무결성 차단
- 200 OK 이지만 0건.
- diff/persist 스킵.
- 마지막 성공 자료로 PARTIAL 리포트.
- 리포트 파일 잠김
- 대상 xlsx 잠김 또는
~$파일 존재. _HHMMSS폴백 이름 사용.- 최신본 갱신 실패는 WARN 으로 기록.
- 대상 xlsx 잠김 또는
- AI 실패 독립성
- agy timeout/schema fail.
- 리포트는 생성되고 AI 없음 배너만 표시.
R4 — xlsx 디자인 감사 RED
DMF Crawler 의 가장 중요한 사용자 화면은 Excel 리포트다. xlsx 는 zip/XML 이므로 stdlib 로 검사한다.
필수 감사:
| 항목 | RED 조건 |
|---|---|
| 파일 구조 | [Content_Types].xml, xl/workbook.xml, worksheets, styles 존재 |
| 시트 순서 | 대시보드가 첫 시트, 메타가 마지막, 워치리스트는 조건부 |
| 시트명 | 00_대시보드 등 SSOT 명칭과 1:1 |
| 탭 색 | report/theme.py 의 TAB_COLORS 반영 |
| 대시보드 viewport | zoom 90, 행/열 폭 합이 1366×768 무스크롤 목표 초과 금지 |
| 틀 고정 | 변경분/전체현황/집계 시트에 freeze panes |
| 자동필터/표 | 데이터 표가 ListObject 또는 autofilter 를 가짐 |
| 내부 링크 | 대시보드 내비, 변경분→전체현황 링크 존재 |
| 조건부서식 | 신규/변경/취하/워치/게이트 실패 서식 존재 |
| 대비 | 팔레트 상태 배경/글자 contrast 최소 AA, 핵심 상태 AAA 목표 |
| 색 단독 금지 | 신규/변경/취하가 라벨+기호+색으로 표현 |
| 긴 텍스트 | 한글 긴 성분명/제조소/소재지 fixture 가 열폭 255 같은 폭주를 만들지 않음 |
| 빈 상태 | 변경 0건, 워치리스트 없음, AI 없음이 깨진 표/빈 차트로 보이지 않음 |
| 출처 | 식약처/공공데이터포털 출처 문구가 메타/푸터에 존재 |
R5 — GUI 디자인·UX 감사 RED
대상: tkinter 온보딩/복구 GUI.
자동화 전략:
- 실제 창을 띄우되 가능하면 withdraw/offscreen 으로 둔다.
- 위젯 트리(
winfo_children)를 검사한다. Entry에 실제 텍스트를 넣고 버튼 command 를 호출한다.- 텍스트 길이를 늘려 레이아웃 clipping 을 검사한다.
- geometry propagation 이후
winfo_reqwidth/height와 screen/컨테이너 크기를 비교한다.
필수 감사 항목:
| 범주 | 실패 조건 |
|---|---|
| 끔찍한 중앙 정렬 | 폼 라벨/입력/버튼이 의미 없이 전체 중앙에 뭉쳐 스캔 불가 |
| 시각적 치우침 | 주요 column/버튼 그룹의 x/y 정렬선이 크게 어긋남 |
| 폰트 | 본문 10pt 미만, 대비 낮은 muted text, 제목/본문 hierarchy 없음 |
| overflow | 긴 한글/영문/API 키/경로가 컨테이너 밖으로 나가거나 잘림 |
| input label | 모든 입력에 visible label 또는 설명 텍스트 없음 |
| input padding | 입력 내부 텍스트와 경계/버튼 간격이 너무 좁음 |
| 실제 입력 | API 키/웹훅/워치리스트 텍스트를 넣어도 깨지지 않음 |
| 버튼 클릭 | [키 입력], [다시 검사], [지금 실행], [로그 열기] command 가 연결됨 |
| 복합 시나리오 | 키 없음→입력→검증 실패→오류 표시→재입력 흐름이 끊기지 않음 |
| 아이콘/텍스트 | 아이콘만 있는 버튼에 텍스트/툴팁 없음, vertical align 어긋남 |
| focus | Tab 순서가 논리적이고 기본 포커스가 위험 버튼에 가지 않음 |
| 모달 | 닫기/나중에/복구 행동이 명확하고 ESC/창닫기 처리됨 |
| 자연스러운 한국어 안내 | 온보딩 상단 안내가 무엇:/왜:/어떻게:/다음: 같은 AI식 라벨을 노출하지 않고, 현재 막힌 항목과 누를 행동을 1~3개의 자연스러운 문장으로 설명 |
R6 — 브라우저 수집 E2E RED
브라우저 수집은 수집용 AGY가 visible/headful Chrome 을 조작하는 경로다. 요약용 AGY와 혼동하지 않는다.
- 전용 Chrome 프로필 Preferences 에 다운로드 경로가 고정된다.
- CDP 포트 소유권 sentinel 을 확인하지 못하면 중단한다.
.tmp/.crdownload가 사라진 뒤 최종 xlsx 만 집는다. (test_headful_browser_collect_rejects_incomplete_downloads)- 다운로드 파일이 최소 크기/zip 구조/행 수 임계 검증을 통과한다. (
test_headful_browser_collect_validates_download_and_returns_raw_records) - CCBAC03 웹 xlsx 헤더 alias(
신청인,제조국가,최초등록일자,최종변경일자)를 RawRecord 표준 필드로 매핑한다. (test_headful_browser_accepts_public_ccbac03_xlsx_headers) - 로그인 상태가 아니면 “세션 만료”로 감지하고 비밀번호를 agy prompt 로 보내지 않는다. (
test_headful_browser_prompt_contract_forbids_headless_http_and_secrets) mcp/execute_urlpermission auto-deny 를 빈 JSON 오류로 뭉개지 않고 원인별로 보고한다. (test_headful_browser_collect_reports_mcp_permission_denial_from_stderr,test_headful_browser_collect_reports_execute_url_permission_denial_from_stderr)- AGY 권한은
mcp(chrome-devtools/*),execute_url(nedrug.mfds.go.kr)만 좁게 추가한다.mcp(*),execute_url(*),--dangerously-skip-permissions금지. (test_headful_mcp_allow_rule_is_scoped_and_does_not_use_dangerous_skip) - 프롬프트는 ambiguous 버튼 클릭 대신 visible Chrome 에서
/pbp/CCBAC03/getExcel을 열고 wrong export 를 guard 한다. (test_headful_browser_prompt_blocks_wrong_review_result_export) source.mode=auto+ API 키 없음은 AGY headful browser 로 라우팅한다. (test_source_auto_without_api_key_routes_to_headful_browser)source.mode=browser는 API 키가 있어도 AGY headful browser 를 강제한다. (test_source_browser_mode_routes_to_headful_even_when_api_key_exists)- 실제 nedrug 접근은 기본 테스트가 아니라 사용 승인 수동 smoke 로 분리한다. 2026-09-03 smoke
run_20260903_130332: fetch/normalize/persist/report SUCCESS, 9,816건.
R7 — 에이전트 지침/정책 RED
AGENTS.md, CLAUDE.md, 이 문서가 서로 어긋나면 agent 가 나쁜 습관으로 돌아간다.
필수 검사 후보:
AGENTS.md가 이 문서를 링크한다.- “No code without RED” 규칙이 존재한다.
- “테스트 약화 금지”가 존재한다.
- “디자인 감사 RED” 체크리스트가 존재한다.
- “실패를 숨기지 말 것”이 존재한다.
CLAUDE.md는 중복 SSOT 가 아니라AGENTS.md/이 문서 포인터다.
3. RED 작성 품질 기준
새 RED 는 아래 8문항을 통과해야 한다.
- 어떤 사용자/운영 위험을 막는가?
- 어느 요구사항/설계 문서를 근거로 하는가?
- 실패했을 때 메시지가 원인을 좁혀 주는가?
- 현재 코드에서 실제로 실패하는가?
- 실패 이유가 기대한 이유인가?
- network/clock/random/shared DB 에 의존하지 않는가?
- 같은 위험을 이미 더 강한 테스트가 덮고 있지 않은가?
- Green 후에도 리팩터링을 방해하지 않는 행동 중심 테스트인가?
통과하지 못하면 RED 가 아니라 메모다.
4. RED 통폐합·삭제 기준
TDD 는 계속 RED 를 늘리는 행위가 아니다. 테스트 스위트도 제품이다.
4.1 통합해야 하는 경우
- 같은 fixture 로 같은 실패를 여러 테스트가 반복한다.
- 단위 테스트와 E2E 가 같은 단순 분기만 검사한다.
- 실패하면 원인이 항상 같은 내부 구현 세부다.
처리:
- 가장 사용자 의미가 강한 테스트 하나를 남긴다.
- 나머지는 helper/fixture 로 합친다.
- 커밋/작업 로그에 “어떤 위험이 어디로 이동했는지” 기록한다.
4.2 삭제해야 하는 경우
- 구현 세부를 강제해 좋은 리팩터링을 막는다.
- flaky 해서 신뢰를 깎는다.
- 요구사항이 사라졌다.
- 더 강한 상위 테스트가 같은 결함을 잡는다.
삭제 금지:
- 단지 Green 을 만들기 어렵다는 이유.
- 실행 시간이 길다는 이유만으로 삭제. 먼저 계층 이동/fixture 축소/impact run 을 시도한다.
4.3 완화해야 하는 경우
디자인 임계값(예: 폭/대비/간격)은 완화 가능하지만, 다음이 있어야 한다.
- 실패 스크린샷 또는 xlsx XML 증거.
- 사용자 유즈케이스에서 문제가 되지 않는 이유.
- 완화 후에도 잡히는 결함 목록.
5. 작업 절차 — 에이전트 필수 프로토콜
5.1 시작 전
docs/HANDOFF.md를 읽는다.- 이 문서와 관련 설계 문서를 읽는다.
- 변경할 파일 목록을 정한다.
- 영향 테스트 목록을 쓴다.
- 아직 테스트가 없으면 먼저 RED 를 작성한다.
5.2 구현 중
- RED 를 실행해 실패 로그를 확인한다.
- 최소 구현으로 Green 을 만든다.
- 관련 테스트를 다시 실행한다.
- Green 상태에서만 리팩터링한다.
- 전체 smoke 를 돌린다.
5.3 보고
보고에는 최소 다음을 포함한다.
- 추가/수정한 RED.
- RED 실패가 기대한 이유였는지.
- Green 을 위해 바꾼 파일.
- 실행한 명령과 결과.
- 남은 실패/미검증 항목.
- 디자인 감사 결과(해당 시).
6. 영향 테스트 지도 v1
| 변경 파일 | 먼저 돌릴 테스트 | 그 다음 |
|---|---|---|
models.py |
전체 import, dataclass contract tests | 전체 pytest |
normalize.py |
tests/test_normalize.py |
diff/integrity/report data |
diff.py |
diff unit tests | pipeline scenario |
integrity.py |
integrity gate tests | pipeline stale fallback |
source_mfds.py |
API fixture parser tests | fetch E2E mock |
storage/db.py |
migration tests | repo/pipeline/report data |
storage/repo.py |
repo roundtrip tests | pipeline/report-only |
pipeline.py |
pipeline fixture E2E | alerts/watchdog/report |
agy/* |
polluted JSON/schema/budget tests | pipeline AI failure independence |
report/data.py |
SQL fixture report data tests | xlsx design audit |
report/*sheets* |
xlsx XML/visual audit tests | report-only smoke |
gui/* |
tkinter structure/input/button tests | onboard smoke |
notify/*, alerts.py |
template 4요소/DB state tests | notify-pump once |
scripts/*.ps1 |
BOM/parser tests | 수동 Windows registration probe |
AGENTS.md, CLAUDE.md |
agent policy contract tests | docs link check |
7. 현재 프로젝트에 즉시 필요한 RED backlog
이전 구현 워크플로우가 중간에 끊겨 있으므로, 아래 RED 부터 만든다.
7.1 R0 계약 RED
test_all_modules_import— 현재 이름:test_all_expected_runtime_entry_modules_import(tests/test_project_contracts.py)test_cli_help_exits_zero(tests/test_project_contracts.py)test_doctor_json_never_crashes_without_key(tests/test_project_contracts.py)test_powershell_scripts_have_bom— 현재 이름:test_powershell_scripts_have_utf8_bom(tests/test_project_contracts.py)test_cmd_files_are_ascii_only(tests/test_project_contracts.py)
7.2 R2 저장소 RED
test_migrations_apply_to_empty_db— 현재 이름:test_migrations_apply_to_empty_db_and_are_checksum_idempotent(tests/test_storage_repo.py)test_repo_baseline_roundtrip— 현재 이름:test_repo_baseline_roundtrip_creates_snapshot_without_events(tests/test_storage_repo.py)test_repo_changed_withdrawn_events_roundtrip— 현재 이름:test_repo_changed_new_withdrawn_events_roundtrip(tests/test_storage_repo.py)test_same_day_success_is_idempotent— 현재 이름:test_same_day_success_is_enforced_by_database_guard(tests/test_storage_repo.py)
7.3 R3 파이프라인 RED
test_run_without_service_key_uses_existing_baseline_instead_of_blockingtest_first_success_run_creates_baseline_reporttest_empty_success_body_blocks_diff_and_preserves_previous_snapshottest_agy_failure_does_not_block_report
7.4 R4 xlsx 디자인 RED
test_report_sheet_order_and_names—tests/test_report_e2e_design.py의 seeded DB → 실제 xlsx XML 감사가 검사한다.test_dashboard_has_no_scroll_viewport_budgettest_report_has_freeze_panes_autofilter_internal_links—tests/test_report_e2e_design.pytest_report_status_styles_are_not_color_only—tests/test_report_e2e_design.py의 상태 라벨/기호/조건부서식 감사가 검사한다.test_report_palette_contrast_ratiostest_long_korean_values_do_not_create_extreme_column_widths—tests/test_report_e2e_design.py
7.5 R5 GUI 디자인 RED
test_onboard_all_inputs_have_visible_labelstest_onboard_can_type_api_key_and_trigger_save_actiontest_recover_message_explains_problem_and_action_in_natural_koreantest_main_actions_have_specific_button_labelstest_long_korean_error_text_does_not_exceed_window_budgettest_tab_order_reaches_primary_actions_before_secondary_actionstest_onboarding_footer_buttons_render_visible_text_after_refresh— 사용자 스크린샷C:\Users\encep\AppData\Local\Temp\pi-clipboard-5df73a57-a813-41be-b025-819e48bd73c2.png회귀. 하단 버튼이 빈 버튼처럼 보이면 실패해야 한다. (tests/test_ux_regressions.py)test_onboarding_footer_buttons_keep_text_when_required_items_exist— 필수/권장 조치가 있는 상태에서도 footer 버튼 텍스트가 짜부라지거나 사라지면 실패해야 한다. (tests/test_ux_regressions.py)test_onboarding_row_action_buttons_are_not_clipped_inside_scroll_view— 행별 액션 버튼이 스크롤 영역/창 하단에 잘리면 실패해야 한다. (tests/test_ux_regressions.py)test_onboarding_new_grouped_layout_keeps_primary_actions_visible— 새 그룹 레이아웃에서 주요 버튼이 아예 안 보이면 실패해야 한다. (tests/test_ux_regressions.py)test_scroll_frame_mousewheel_scrolls_when_pointer_is_over_content— scroll canvas 안의 실제 텍스트/카드 위에서 휠을 굴려도 스크롤되어야 한다. (tests/test_ux_regressions.py)test_onboarding_status_panel_names_blockers_instead_of_generic_yellow_info_box— 상단 안내문은무엇:/왜:/어떻게:/다음:같은 AI식 라벨이 아니라 자연스러운 한국어 문장이어야 한다. (tests/test_ux_regressions.py)test_agy_google_login_is_optional_when_ai_disabled_for_api_mode— 공식 API만 쓰고 AI 요약이 꺼져 있으면 AGY/Google 로그인을 요구하지 않는다. (tests/test_ux_regressions.py)test_agy_is_prompted_for_headful_collection_when_auto_has_no_api_key— auto+API 키 없음이면 AI 요약이 꺼져 있어도 최신 수집용 headful AGY 준비를 안내한다. (tests/test_ux_regressions.py)test_agy_is_prompted_for_forced_browser_mode_even_with_api_key— browser 모드는 API 키가 있어도 수집용 headful AGY 준비를 안내한다. (tests/test_ux_regressions.py)
7.6 R6 브라우저 수집 RED
test_headful_browser_prompt_contract_forbids_headless_http_and_secretstest_headful_browser_collect_validates_download_and_returns_raw_recordstest_headful_browser_collect_rejects_incomplete_downloadstest_headful_browser_collect_reports_mcp_permission_denial_from_stderrtest_headful_browser_collect_reports_execute_url_permission_denial_from_stderrtest_headful_mcp_allow_rule_is_scoped_and_does_not_use_dangerous_skiptest_headful_browser_prompt_blocks_wrong_review_result_exporttest_headful_browser_accepts_public_ccbac03_xlsx_headerstest_source_auto_without_api_key_routes_to_headful_browsertest_source_browser_mode_routes_to_headful_even_when_api_key_exists- 전용 Chrome profile Preferences / CDP 포트 sentinel / 스케줄러 headful smoke RED
8. 이 문서와 다른 문서의 관계
| 문서 | 관계 |
|---|---|
docs/00-REQUIREMENTS.md |
사용자 요구 SSOT. 이 문서는 테스트 운영 방법 SSOT. |
docs/design/01-architecture.md |
아키텍처 SSOT. 이 문서는 검증 게이트를 추가한다. |
docs/design/03-xlsx-report-spec.md |
xlsx 디자인 명세 SSOT. R4 RED 는 이 문서를 실행 가능하게 만든다. |
docs/design/04-onboarding-wizard.md |
GUI 명세 SSOT. R5 RED 는 이 문서를 실행 가능하게 만든다. |
docs/research/11-tdd-red-and-design-audit-theory.md |
이론/근거 아카이브. |
AGENTS.md |
AI agent 실무 지침. 이 문서를 압축해 적용한다. |
CLAUDE.md |
Claude Code 진입 포인터. 중복 정본이 아니다. |
9. 미해결 항목
tests/test_project_contracts.py를 만들어 R0 계약 RED 를 실제 코드화한다.- xlsx XML 감사 RED 를 실제 코드화한다. 현재 파일:
tests/test_report_e2e_design.py. - tkinter 구조/입력/버튼 감사 RED 를 실제 코드화한다. 현재 파일:
tests/test_gui_design_contracts.py,tests/test_ux_regressions.py. - 브라우저 수집 경로 확정 후 R6 RED 를 코드화한다. 현재 파일:
tests/test_agy_headful_collection.py. - 영향 테스트 지도를 자동 생성/검증할지 결정한다(TDAD 방식의 단순 텍스트 맵부터 시작).