테스트 도구 실전 · 예외를 주입해 자원 누수를 찾는다 · 实验
예외를 주입해 자원 누수를 찾는다
목표
정상·중복 종료·실패 경로와 앱 lifespan의 시작·정리를 관측합니다.
왜 중요한가
요청이 성공하면 연결을 닫는 코드는 있었지만 예외가 발생하면 열린 연결이 남았다. 성공 응답만 확인하는 테스트에서는 이 누수가 드러나지 않았다. 수명 주기 테스트는 반환값보다 자원이 언제 열리고 닫혔는지에 집중해야 한다.
단계
1. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: new_resource()는 {open:False, events:[]}인 새 딕셔너리이며 호출끼리 events를 공유하지 않습니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.
mkdir -p /root/work/test-lifespan-cleanup-labtest -e /root/work/test-lifespan-cleanup-lab/service.py || cp /opt/fixtures/ten_labs/test-lifespan-cleanup-lab/service.py /root/work/test-lifespan-cleanup-lab/service.pytest -e /root/work/test-lifespan-cleanup-lab/test_service.py || cp /opt/fixtures/ten_labs/test-lifespan-cleanup-lab/test_service.py /root/work/test-lifespan-cleanup-lab/test_service.pycd /root/work/test-lifespan-cleanup-lab2. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: start(resource)는 이미 열려 있으면 ValueError, 아니면 open=True로 바꾸고 events에 'open'을 추가합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
3. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: stop(resource)는 열려 있을 때만 open=False로 바꾸고 'close'를 events에 추가합니다. 이미 닫혀 있으면 그대로 둡니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
4. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: read(resource)는 닫혀 있으면 RuntimeError, 열려 있으면 {ready:True}를 반환합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
5. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: scope(resource)는 contextmanager입니다. 진입 시 start, 블록 안에는 resource를 yield하고 블록의 성공·실패 모두 stop으로 닫습니다. 블록 예외는 전파합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
6. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: lifespan_for(resource)는 asynccontextmanager 함수 lifespan(app)을 반환합니다. scope(resource) 안에서 app.state.resource를 설정하고 yield합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
7. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: create_app(resource)는 lifespan_for를 사용합니다. GET /ready는 app.state.resource를 read한 결과를 반환합니다. context 종료 시 자원을 닫아야 합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.
8. /root/work/test-lifespan-cleanup-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: exercise(resource, fail=False)는 with TestClient(create_app(resource)) 안에서 GET /ready를 호출합니다. fail=True면 그 안에서 RuntimeError를 내고, 아니면 응답 JSON을 반환합니다. 두 경우 모두 자원이 닫혀야 합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 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)
- 한계: 교육용 자원 사전은 실제 DB 연결 풀을 대신하는 관측 장치다. 운영에서는 부분 초기화 실패, 연결 풀의 동시성, 취소 처리와 종료 제한 시간도 설계해야 한다. 이벤트 문자열을 보고서에 적는 것이 아니라 학습자 코드가 실행하며 바꾼 객체 상태를 검사한다. 제공 구현은 읽어도 되지만 채점은 별도 사본을 사용한다. 소스 문구 검사나 파일 수정으로 결함을 우회하지 말고 공개 인터페이스의 실행 결과를 검사한다.
8个步骤
- 자원 상태를 독립적으로 만든다 — 테스트
- 중복 시작을 거절한다 — 테스트
- 종료를 멱등하게 만든다 — 테스트
- 닫힌 자원의 사용을 막는다 — 테스트
- 예외 경로에 finally를 둔다 — 테스트
- 앱 수명주기와 자원을 연결한다 — 테스트
- 준비 상태를 실제 요청으로 읽는다 — 테스트
- 요청 이후의 실패도 정리한다 — 테스트