새 작업 폴더에서 재현
100분 안팎
학습 목표
준비된 캐시와 별도 임시 폴더에서 최종 앱의 오프라인 테스트를 실행합니다.
개념
새 폴더 검사는 어떤 착각을 제거하나요
작업 폴더에는 전에 만들어진 class 파일과 테스트 보고서가 남습니다. 압축에서 파일이 빠져도 이전 결과가 보이면 성공처럼 읽을 수 있습니다. 새 작업 폴더 검사는 전달한 소스만 복사해 다시 컴파일하고 검사를 수행하여 이런 의존을 줄입니다. 이번 목표는 완전히 새 컴퓨터에서의 설치가 아니라 준비된 JDK와 Maven 캐시를 공유하는 별도 임시 폴더의 재현입니다. 캐시를 쓰는 조건을 숨기지 않아야 받는 사람이 환경 실패와 앱 결함을 구분할 수 있습니다.
무엇을 복사하고 무엇을 제외하나요
소스, pom.xml, mvnw와 숨김 디렉터리 .mvn/wrapper를 보존합니다. target, __pycache__, .git, 이전 evidence와 loop-report.json은 새 실행 입력에서 제외합니다. shell의 별표 복사는 숨김 디렉터리를 빠뜨릴 수 있으므로 제공 함수는 shutil.copytree를 사용합니다. 제외 목록은 파일 이름 집합으로 적용합니다. target을 제외하는 이유는 빌드 결과 재사용을 막기 위해서이며 .mvn을 제외하면 실행 도구 자체를 잃습니다. 학습자는 starter의 TODO 목록만 수정하고 기존 앱과 검사기를 보존합니다.
원본을 지우지 않고 독립된 사본을 만듭니다
TemporaryDirectory 안에 app이라는 새 폴더를 만들고 다운로드 루트에서 복사합니다. 명령의 cwd를 그 사본으로 지정하면 검사 중 생성된 target과 로그가 원본에 섞이지 않습니다. with 블록을 나오면 이번 호출이 만든 임시 폴더가 정리됩니다. 원본 target이나 공유 ~/.m2를 지우는 정리 명령은 필요하지 않습니다. 실패를 재현하려면 종료 코드와 로그를 원본 evidence에 저장합니다. 실패 폴더를 계속 쌓는 대신 재현 명령과 입력 파일을 남기는 방식입니다.
명령 결과를 다음 관문에 전달합니다
검사 실행은 인자 배열로 subprocess.run에 전달합니다. command 문자열에 임의 입력을 붙이지 않아도 됩니다. 기존 check.sh는 명세 범위, Java 회귀, 참조 모델, 프로젝트 지침, 하네스와 권한 및 루프 단위 검사를 수행합니다. 프로그램 종료 코드가 0이어야 다음 검사로 갑니다. 하나라도 실패하면 그 종료 코드를 반환하고 뒷단의 연결 감사는 실행하지 않습니다. 로그 마지막에 성공이라는 단어가 있어도 종료가 실패라면 통과로 바꾸지 않는 규칙을 유지합니다.
보고서가 이번 실행에서 만들어졌는지 봅니다
새 폴더에 target이 없으므로 Surefire XML은 현재 실행으로 만들어집니다. 결과 파일이 하나도 없거나 테스트 수가 0이면 성공 종료만으로 통과시키지 않습니다. failures, errors, skipped 중 하나가 있으면 완료 근거로 받아들이지 않습니다. 건너뛴 검사가 항상 앱 결함을 뜻하는 것은 아니지만 이번 수료 기준은 제공된 회귀를 전부 실행하는 것입니다. clean-run.json의 테스트 수와 종료 코드를 읽고 해당 파일이 현재 실행에서 갱신되었는지 확인합니다. 과거 보고서 복사는 이 관문을 우회합니다.
복사 계약은 빠른 작은 검사로 검증합니다
test_clean.py는 오래된 target과 evidence 제외, 숨김 wrapper 보존, 원본 유지, 실패 종료 코드 전파, 실패 후 추가 실행 중단, 성공을 각각 확인합니다. 대부분 임시 합성 파일과 짧은 Python 명령을 사용하므로 Maven을 매번 돌리지 않습니다. 이 여섯 검사가 먼저 통과하면 실제 새 폴더에서 기존 check.sh 전체를 실행합니다. 빠른 계약 검사와 실제 앱 재현은 확인 범위가 다릅니다. 단위 검사 성공만으로 Java 앱까지 실행했다고 쓰지 않으며 실제 clean_run 결과를 별도로 기록합니다.
환경 오류의 첫 줄을 분류합니다
Permission denied가 mvnw에서 나오면 실행 권한과 압축 해제 상태를 먼저 봅니다. .mvn/wrapper 관련 파일 없음은 숨김 파일 누락을 의심합니다. Cannot access와 offline mode가 함께 보이면 로컬 캐시에 필요한 의존성이 없는지 확인합니다. 이때 테스트 단언을 지우지 않습니다. JDK가 다르면 java -version으로 주 버전을 확인하고 요구 환경으로 재실행합니다. 이러한 환경 오류는 요구사항 결함이라는 증거가 아니며 실행 실패 또는 환경 미확인으로 기록합니다. 해결 뒤 같은 명령을 다시 실행합니다.
단언 실패는 입력과 기대값으로 읽습니다
AssertionError의 expected와 actual은 프로그램을 실행한 결과가 계약과 달랐다는 뜻입니다. 복사 계약에서 target이 남았다는 실패는 앱 로직 오류가 아니라 전달 방식 오류입니다. 제목 순서 검사에서 [1,2] 대신 [2,1]이면 조회 정렬과 명세를 살핍니다. 컴파일 오류는 테스트가 실행되기 전에 멈춘 상태이므로 테스트 실패 개수로만 설명하지 않습니다. 실패 원인을 분류하고 재현 조건을 적으면 AI에게 맡길 수정 요청도 작아집니다. 결과 파일이 있는지 확인한 뒤 검토 판단으로 넘어갑니다.
테스트 재현과 서비스 기동을 분리합니다
standalone MockMvc는 실제 소켓을 열지 않고 컨트롤러의 HTTP 매핑과 응답을 검사합니다. 이번 명령은 Maven 테스트가 끝나면 종료하며 서버를 남기지 않습니다. 테스트 통과는 실제 포트 접근이나 Spring 전체 빈 구성, 동시 요청 처리를 확인했다는 뜻이 아닙니다. pom에는 학습용 의존성이 있고 운영 배포 산출물을 만드는 구성은 이번 범위가 아닙니다. 받는 사람이 실행 방법을 잘못 이해하지 않도록 README에는 테스트 방법과 미확인 서비스 기동을 별도 문장으로 적습니다.
제출 전 수신자의 순서로 확인합니다
새 ZIP을 풀었다고 가정하고 README-delivery.md에서 시작합니다. 기존 README는 앞 단계 기록이므로 이번 전체 명령은 추가 문서에서 찾습니다. starter 실행에서 복사 계약 일부 실패를 확인하고 목록을 수정한 뒤 전부 재실행합니다. clean-run.json을 열어 tests가 양수이고 오류와 건너뛴 검사가 없는지 대조합니다. 자동 연결 감사가 통과해도 최종 의미 검토가 남았다고 표시합니다. Maven 빌드의 일반적인 의존성 및 배포 설명은 연결된 서재 원고에서 더 읽습니다.
따라하기
숨김 파일 누락을 재현합니다
합성 폴더에서 별표 선택과 숨김 wrapper 존재를 비교합니다. 실제 Maven은 호출하지 않습니다.
from pathlib import Path
from tempfile import TemporaryDirectory
import glob
with TemporaryDirectory() as tmp:
root=Path(tmp)
(root/'.mvn').mkdir()
(root/'src').mkdir()
print('star:', sorted(Path(p).name for p in glob.glob(str(root/'*'))))
print('wrapper directory:', (root/'.mvn').is_dir())실행 결과
star: ['src'] wrapper directory: True
새 복사본의 입력을 검사합니다
오래된 target을 제외하고 숨김 .mvn을 남기는 예시를 실행합니다.
import shutil
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
source=Path(tmp)/'source'; source.mkdir()
(source/'target').mkdir(); (source/'.mvn').mkdir(); (source/'src').mkdir()
target=Path(tmp)/'copy'
shutil.copytree(source,target,ignore=shutil.ignore_patterns('target'))
print('copy:', sorted(p.name for p in target.iterdir()))
print('original target:', (source/'target').exists())실행 결과
copy: ['.mvn', 'src'] original target: True
실패를 다음 실행에 전달합니다
짧은 fixture 명령이 실패하면 뒤 명령을 실행하지 않습니다. 이 결과는 앱 테스트 통과 근거가 아닙니다.
import subprocess,sys
commands=[[sys.executable,'-c','raise SystemExit(7)'],[sys.executable,'-c','print(99)']]
executed=0
for command in commands:
code=subprocess.run(command,capture_output=True).returncode
executed+=1
if code: break
print('exit:',code)
print('executed:',executed)실행 결과
exit: 7 executed: 1
실제 새 폴더 검사를 실행합니다
수정한 starter ZIP 루트에서 실행합니다. 아래는 작성자가 solution으로 실행한 표준 출력이며 unittest 결과는 표준 오류에 따로 표시됩니다. 의존성 캐시가 준비된 조건입니다. clean-run.json에서 새 Java 테스트 54개와 루프 결과를 읽고 자신의 실행 결과로 인계서를 갱신합니다.
LAB_OFFLINE=1 bash delivery_check.sh실행 결과
CLEAN RUN PASS EVIDENCE LINKS PASS (6); semantic review remains human
확인 문제
실습
clean_run.py의 TODO 제외 목록을 수정하여 target·이전 evidence·loop-report.json을 새 폴더에 복사하지 않습니다. .mvn/wrapper와 원본은 보존합니다. evidence/requirements.json의 H3 누락은 test_loop의 실제 중단 사례와 README-m09.md로 연결합니다. 기존 앱·테스트·정책은 유지합니다. 복사 계약 6개, 새 폴더 check.sh 전체, 근거 연결 6행이 통과하고 인계 문서의 의미는 직접 검토합니다.
실행 명령
LAB_OFFLINE=1 bash delivery_check.sh
기대 결과
CLEAN RUN PASS와 EVIDENCE LINKS PASS (6); 복사 계약 6개·새 Java 회귀·기존 Python 검사 모두 성공. 사람 의미 검토는 별도입니다.
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- AI가 만든 코드가 실행될 때 추가로 확인할 내용을 설명해 주시면 됩니다.