테스트 도구 실전 · 오류 응답의 회귀를 잡는 계약 테스트 · 실습
오류 응답의 회귀를 잡는 계약 테스트
목표
알려진 오류·알 수 없는 오류·비공개 정보·추적 헤더를 테스트합니다.
왜 중요한가
오류 메시지 변경 뒤 모바일 앱의 재시도 로직이 망가졌다. 테스트는 예외가 발생했다는 사실만 확인했고 상태와 공개 본문의 의미는 비교하지 않았다. 내부 예외가 사용자에게 그대로 노출되는 회귀도 같은 틈을 통과했다.
단계
1. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: DomainError(code, message)는 Exception 하위 클래스이며 .code에 code를 보관합니다. str(예외)는 message입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/test-error-contract-labtest -e /root/work/test-error-contract-lab/service.py || cp /opt/fixtures/ten_labs/test-error-contract-lab/service.py /root/work/test-error-contract-lab/service.pytest -e /root/work/test-error-contract-lab/test_service.py || cp /opt/fixtures/ten_labs/test-error-contract-lab/test_service.py /root/work/test-error-contract-lab/test_service.pycd /root/work/test-error-contract-lab2. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: status_for(code)는 missing=404, conflict=409, invalid=422, 그 외=500입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
3. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: public_message(code)는 missing='Resource not found', conflict='State conflict', invalid='Invalid request', 그 외='Internal error'입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
4. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: request_id(value)는 ASCII 영문·숫자·밑줄·하이픈으로만 이루어진 1~32자 문자열이면 그대로, 아니면 'untracked'입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
5. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: problem(code, rid)는 type='urn:labhub:problem:'+code, title와 detail=public_message(code), status=status_for(code), request_id=request_id(rid)만 가진 딕셔너리입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
6. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: response_for(code, rid)는 problem을 본문으로, status_for를 상태로, application/problem+json을 media_type으로, X-Request-ID를 정규화한 rid로 둔 JSONResponse입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
7. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: install_handlers(app)는 DomainError 핸들러를 등록합니다. 헤더 X-Request-ID를 읽고 exc.code에 대해 response_for를 반환합니다. exc의 message는 응답에 넣지 않습니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
8. /root/work/test-error-contract-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: create_app()은 핸들러를 설치하고 GET /fail/{code}에서 DomainError(code, 내부문장)를 냅니다. 단 code=boom이면 RuntimeError를 냅니다. RuntimeError 핸들러는 code=internal인 고정 500 문제 응답을 반환합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
참고
- 인터넷과 패키지 설치 없이 기존 lab-dev 환경에서 수행합니다.
- 각 단계는 45초 채점 예산 안에서 실행됩니다. 실제 sleep이나 네트워크 호출을 추가하지 마세요.
- 제출 테스트는 별도 임시 폴더에서 정상·결함 구현에 실행합니다. 정상에서는 실제 실행한 테스트가 모두 통과하고 결함에서는 테스트 본문이 실패해야 합니다. 수집 오류, 실행 0개, 전부 건너뜀, 강제 종료는 통과가 아닙니다. pytest 기본 기능과 제공 라이브러리만 사용하세요.
- [FastAPI 공식 문서](https://fastapi.tiangolo.com/) · [pytest 공식 문서](https://docs.pytest.org/en/stable/) · [Python sqlite3](https://docs.python.org/3/library/sqlite3.html)
- 한계: 이 실습의 문제 응답은 type·title·status·detail·request_id를 갖는 교육용 계약이다. 범용 국제화와 전체 표준 적합성을 주장하지 않는다. request id는 추적에 쓰는 문자열이지 인증 수단이 아니며, 운영 로그에도 비밀값을 그대로 기록해서는 안 된다. 제공 구현은 읽어도 되지만 채점은 별도 사본을 사용한다. 소스 문구 검사나 파일 수정으로 결함을 우회하지 말고 공개 인터페이스의 실행 결과를 검사한다.
8단계
- 업무 예외에 code를 남긴다 — 테스트
- code를 상태로 매핑한다 — 테스트
- 공개 문장을 고정한다 — 테스트
- 요청 id를 제한한다 — 테스트
- 문제 본문을 만든다 — 테스트
- 응답 형식을 일관되게 만든다 — 테스트
- 업무 예외 핸들러를 연결한다 — 테스트
- 예상하지 못한 오류도 숨긴다 — 테스트