Devin.KR

새 폴더에서 재현하기

75분 안팎

학습 목표

숨은 경로와 의존성을 찾습니다.

개념

내 폴더의 숨은 준비를 제거합니다

작성 폴더에서 보고서가 나오는데 새로 받은 폴더에서는 파일이 없다는 오류가 날 수 있습니다. import를 미리 실행해 둔 records.json이나 개인 컴퓨터의 report.sql 경로가 숨어 있었기 때문입니다. 새 폴더 재현은 필요한 원본만 가져와 처음 상태에서 명령을 실행하는 검증입니다. 한 번 더 실행하는 것보다 무엇을 가져갔고 무엇을 새로 만들었는지가 중요합니다.

앞 레슨의 README 절차는 CSV를 먼저 가져온 뒤 JSON 조회와 보고서를 실행합니다. 이번 clean_run.py는 그 순서의 명령을 새 임시 폴더에서 수행합니다. 임시 폴더는 실행마다 새로 생성하고 끝나면 컨텍스트가 정리합니다. 저장소 작업 폴더를 지우거나 이전 자료를 덮어쓰지 않습니다. 서버를 실행하지 않으므로 프로세스 종료 명령도 필요하지 않습니다.

입력·코드·생성물의 경계를 만듭니다

복사 목록은 file_cli.py, persistence.py, store.py, records.py, parsing.py, reporting.py, report.sql, data/import.csv입니다. Python 파일만 복사하면 SQL도 코드 실행의 자원이라는 사실을 놓칩니다. 미추적 records.json과 __pycache__는 복사하지 않습니다. 이 목록은 파일 CLI의 실행 자원이며 전체 프로젝트 전달 목록과 같지는 않습니다. HTTP 관찰 문서와 테스트는 미션 전체 전달에 별도로 포함합니다.

원본 CSV는 제공한 가상 자료이며 JSON은 가져오기 명령이 새로 생성하는 결과물입니다. 성공 뒤 생성 JSON이 있다는 것만으로는 원본을 제대로 읽었는지 알 수 없습니다. 가져온 건수와 조회 필드를 확인하고 공개 보고서의 행 수를 대조합니다. 빈 생성 파일을 미리 넣어서 missing 오류를 없애는 방식은 숨은 의존성을 해결하지 못합니다.

Git 전달의 기준은 추적 파일입니다. 개인 작업 트리에는 기록하지 않은 파일이 있어도 코드는 실행될 수 있습니다. git ls-files로 CSV·SQL·테스트·문서가 추적되었는지 확인하고 git status --short로 미선택 변경을 살핍니다. 깨끗한 상태라도 생성 파일이 ignore에 들어 있으면 여전히 디스크에 있을 수 있습니다. 따라서 원본 폴더의 상태 확인과 새 폴더 실행을 함께 수행합니다.

경로를 두 관점에서 검사합니다

reporting.py는 기본 report.sql을 자기 파일의 부모 폴더에서 찾습니다. 이는 어느 작업 디렉터리에서 실행하든 배포한 SQL을 읽으려는 선택입니다. CLI 인자의 상대 경로는 사용자의 작업 디렉터리 기준입니다. 둘 중 하나만 검사하면 다른 종류의 위치 오류가 남습니다. test_cli.py는 다른 폴더에서 절대 스크립트 경로와 임시 자료를 사용하며 clean_run.py는 새 프로젝트 폴더 안에서 상대 명령을 실행합니다.

FileNotFoundError가 나면 traceback 마지막 줄뿐 아니라 어떤 명령 단계에서 실패했는지 확인합니다. 새 폴더의 import는 성공하고 report에서 실패했다면 원본 CSV보다 SQL 자원을 먼저 봅니다. CLI가 파일 오류를 요약해 출력하므로 copy 목록과 보고서 기본 경로를 함께 읽습니다. 실제 사용자 경로를 오류 메시지에 더 붙이지 않고 개발 환경에서 가상 경로로 원인을 재현합니다.

starter의 실패를 필요한 자원으로 해결합니다

starter는 clean_run.py의 FILES에서 report.sql을 빠뜨렸습니다. 파일 명령 일곱 테스트는 원본 실습 폴더에 SQL이 있어서 통과하지만 새 폴더 보고서는 ERR_FILE|FileNotFoundError로 실패합니다. 이 차이가 실습의 핵심입니다. 보고서 검사를 삭제하거나 실패를 성공으로 바꾸지 않습니다. 복사 목록에 SQL을 추가하고 새 폴더 검사를 다시 실행합니다.

solution은 가져오기, 조회, 보고서가 모두 성공한 뒤 CLEAN|import|lookup|report|PASS를 출력합니다. subprocess의 returncode가 0인지 먼저 확인하고 출력 JSON을 해석하여 실제 값도 검사합니다. 출력 문자열에 PASS만 포함되면 성공으로 보는 방식은 피합니다. 결과가 다르면 첫 실패 명령의 stdout·stderr를 제시하므로 어느 단계에서 멈췄는지 알 수 있습니다.

실습 명령은 일곱 CLI 테스트를 실행한 후 clean_run.py를 실행합니다. starter는 일곱 검사는 통과하고 마지막 검사에서 실패합니다. solution은 두 검사 흐름 모두 성공합니다. 이는 전체 HTTP 동작을 검사한 결과가 아닙니다. 앞 모듈의 HTTP 검사는 소켓 사용이 가능한 환경에서 별도로 확인해야 하며 이번 파일 검증 결과를 그 증거로 대신하지 않습니다.

실패 입력과 재실행도 확인합니다

0쪽 기록은 정상입니다. 없는 ID는 NOT_FOUND와 종료 코드 1이며 없는 CSV는 ERR_FILE|FileNotFoundError와 종료 코드 2입니다. 헤더만 있는 CSV는 IMPORTED|0으로 성공합니다. 음수 쪽수가 있으면 기존 JSON 바이트를 보존해야 합니다. 정상 흐름만 재현해서는 이 경계를 보장할 수 없으므로 파일 테스트와 새 폴더 검사를 함께 사용합니다.

전체 교체 import는 같은 가상 CSV로 두 번 실행하면 내용이 같지만 다른 CSV를 실행하면 저장 목록이 바뀝니다. 이 제한을 README에 알리고 실제 자료에는 별도 보관 정책이 필요하다고 설명합니다. 새 폴더 검사가 매번 새 환경을 만드는 이유는 이전 실행의 JSON에 의존하지 않기 위해서입니다. 재실행 성공과 신규 환경 성공을 구분해 실행 기록에 씁니다.

명령 한 개의 성공보다 전달 계약을 살핍니다

미션은 앞 모듈의 전체 파일을 보존한 상태에서 file_cli.py, README, clean_run.py를 완성합니다. 원래 check.sh가 전체 unittest를 다시 호출하면 이전 test_input이 check.sh를 호출하는 구조와 순환할 수 있습니다. 이 미션은 check.sh를 초기 작업 공간 검사로 두고 check-reproduction.sh에 전체 회귀 실행을 분리합니다. 기존 테스트를 지우는 해결 대신 호출 관계를 정리합니다.

전체 명령은 HTTP 테스트와 임시 저장소 Git 기록까지 포함하므로 외부 검증으로 표시합니다. 파일 기반 새 검사와 HTTP를 제외한 기존 회귀는 이 작성 환경에서 따로 실행합니다. Git 예시 스크립트 검사는 자동 메시지로 기록을 만들지만 학습자의 실제 변경 이유까지 판단하지는 않습니다. 자신이 수행한 변경·근거와 아직 수행하지 않은 동료 검토를 docs/reproduction.md에 정직하게 적습니다.

최종 제출 전에는 추적 파일 목록, README 명령, 실제 출력 요약, 실패 입력 보존, 외부 검증 대기를 대조합니다. 개인 절대 경로가 남아 있지 않은지와 생성 파일이 전달물에 섞이지 않았는지도 봅니다. 레슨을 마치면 새 폴더 실패가 코드 오류인지 전달 자원 누락인지 설명하고 누락을 검사로 드러낼 수 있어야 합니다. 가상 환경·패키지 배포의 자세한 내용은 더 읽기로 이어갑니다.

따라하기

파일 위치와 작업 위치 비교

임시 폴더에 가상 배포 구조를 만들고 코드 위치 기준 SQL과 작업 위치 기준 JSON이 구분되는지 확인합니다.

from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as d:
    root = Path(d)
    script = root / "project" / "file_cli.py"
    cwd = root / "work"
    print((script.parent / "report.sql").relative_to(root))
    print((cwd / "records.json").relative_to(root))

실행 결과

project/report.sql
work/records.json

SQL도 전달 자원입니다

Python 파일만 고르면 빠지는 자원을 집합 차이로 확인합니다. 실제 실습에서는 FILES 목록을 고칩니다.

required = {"file_cli.py", "reporting.py", "report.sql", "data/import.csv"}
selected = {"file_cli.py", "reporting.py", "data/import.csv"}
print("MISSING|" + "|".join(sorted(required - selected)))

실행 결과

MISSING|report.sql

이전 생성물이 없는 상태에서 시작

임시 폴더가 비어 있고 필요한 원본만 새로 넣는 상황을 관찰합니다. 실제 데이터 계약은 CLI 검사에서 대조합니다.

from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as d:
    root = Path(d)
    print("generated_before", (root / "records.json").exists())
    (root / "import.csv").write_text("id,title,pages,cost,completed\n", encoding="utf-8")
    print("provided", sorted(p.name for p in root.iterdir()))

실행 결과

generated_before False
provided ['import.csv']

새 폴더의 보고서까지 실행

starter의 FILES에 report.sql을 추가합니다. 일곱 CLI 검사 다음에 새 폴더 검사까지 성공해야 합니다. 성공 표시는 CLEAN|import|lookup|report|PASS입니다.

python3 -m unittest discover -s tests -p "test_cli.py"
python3 clean_run.py

확인 문제

실습

clean_run.py의 FILES 목록에서 빠진 실행 자원을 찾아 추가합니다. 원본 reports나 생성 JSON을 복사해 문제를 감추지 않습니다. unittest와 clean_run.py의 결과 비교는 바꾸지 않습니다. 새 임시 폴더의 가져오기·0쪽 조회·공개 보고서가 모두 성공해야 합니다. 이 파일 실습은 HTTP나 Git 커밋을 실행하지 않습니다.

시작 코드·테스트 내려받기

실행 명령

python3 -m unittest discover -s tests -p "test_cli.py" && python3 clean_run.py

기대 결과

starter는 CLI 7개 통과 후 report.sql 누락 실패, solution은 7개 통과와 CLEAN|import|lookup|report|PASS.

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 다른 사람이 프로그램을 실행하도록 준비한 내용을 설명합니다.
  • 파일 경로 때문에 실행이 실패했을 때 확인 순서를 설명합니다.