LabHub
배우기 러닝패스 코스

Testing Tools in Practice

Make lazy export behavior observable

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

행 소비 횟수·개행 이스케이프·공개 필드·응답 형식을 검증합니다.

왜 중요한가

작은 리스트로 테스트한 내보내기 코드는 정상처럼 보였지만 큰 입력을 모두 list로 바꾸며 메모리를 소진했다. 결과가 같은 것만으로 실행 방식의 계약까지 같다고 할 수 없다. 지연 소비를 눈으로 보이는 상태로 바꿔야 한다.

단계

  1. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: validate_row(row)는 dict이며 id가 bool 제외 양의 int, name이 비어 있지 않은 str일 때 row를 반환합니다. 나머지는 ValueError입니다. 추가 내부 필드는 허용합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.

mkdir -p /root/work/test-lazy-export-lab
test -e /root/work/test-lazy-export-lab/service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/service.py /root/work/test-lazy-export-lab/service.py
test -e /root/work/test-lazy-export-lab/test_service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/test_service.py /root/work/test-lazy-export-lab/test_service.py
cd /root/work/test-lazy-export-lab
  1. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: project(row)는 validate_row 후 id와 name만 가진 새 dict를 반환합니다. 원본 내부 필드는 그대로 보존합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

  2. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: encode_line(row)는 project 결과를 ensure_ascii=False, separators=(',',':'), sort_keys=True로 JSON 인코딩하고 마지막에 ' ' 한 개를 붙인 str입니다. name 안 개행은 JSON 이스케이프여야 합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

  3. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: validate_max(value)는 bool 제외 int 1~1000만 그대로 반환하고 그 외 ValueError입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

  4. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: take_rows(rows, maximum)는 islice 등으로 최대 maximum개만 지연 반환하는 iterator입니다. 호출 시 maximum을 검증하며 한 번 next하면 입력을 한 번만 소비합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

  5. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: json_lines(rows, maximum=100)는 take_rows에서 받은 행마다 encode_line을 yield합니다. 전부 합친 문자열이나 리스트를 반환하지 않습니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

  6. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: decode_lines(text)는 splitlines의 각 비어 있지 않은 줄을 json.loads 후 validate_row하고 리스트로 반환합니다. 빈 문자열은 [], 비어 있는 중간 줄은 ValueError입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

  7. /root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: create_app(rows)는 GET /export에서 json_lines(rows, 100)을 application/x-ndjson StreamingResponse로 반환합니다. rows는 다시 순회 가능한 리스트입니다. 내부 필드가 없고 각 행의 내용과 순서를 보존해야 합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

참고

행 계약을 검증한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: validate_row(row)는 dict이며 id가 bool 제외 양의 int, name이 비어 있지 않은 str일 때 row를 반환합니다. 나머지는 ValueError입니다. 추가 내부 필드는 허용합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

처음 한 번 준비하세요. 기존 파일은 덮어쓰지 않습니다.

mkdir -p /root/work/test-lazy-export-lab
test -e /root/work/test-lazy-export-lab/service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/service.py /root/work/test-lazy-export-lab/service.py
test -e /root/work/test-lazy-export-lab/test_service.py || cp /opt/fixtures/ten_labs/test-lazy-export-lab/test_service.py /root/work/test-lazy-export-lab/test_service.py
cd /root/work/test-lazy-export-lab

bool과 숫자를 구분하고 빈 이름을 오류로 처리합니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/01-contract.sh로 확인하세요.

공개 행만 만든다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: project(row)는 validate_row 후 id와 name만 가진 새 dict를 반환합니다. 원본 내부 필드는 그대로 보존합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

내보내기 경로도 일반 API와 같은 공개 필드 정책을 적용해야 합니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/02-contract.sh로 확인하세요.

줄 경계를 보존하며 인코딩한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: encode_line(row)는 project 결과를 ensure_ascii=False, separators=(',',':'), sort_keys=True로 JSON 인코딩하고 마지막에 ' ' 한 개를 붙인 str입니다. name 안 개행은 JSON 이스케이프여야 합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

문자열 덧붙이기로 JSON을 만들면 따옴표와 개행에서 형식이 깨집니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/03-contract.sh로 확인하세요.

출력 개수 상한을 검증한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: validate_max(value)는 bool 제외 int 1~1000만 그대로 반환하고 그 외 ValueError입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

무한 입력을 실수로 끝까지 읽지 않도록 호출자에게 상한을 요구합니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/04-contract.sh로 확인하세요.

필요한 행만 소비한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: take_rows(rows, maximum)는 islice 등으로 최대 maximum개만 지연 반환하는 iterator입니다. 호출 시 maximum을 검증하며 한 번 next하면 입력을 한 번만 소비합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

list(rows)로 바꾸는 순간 무한 입력과 대용량 입력을 처리할 수 없습니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/05-contract.sh로 확인하세요.

행을 지연 직렬화한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: json_lines(rows, maximum=100)는 take_rows에서 받은 행마다 encode_line을 yield합니다. 전부 합친 문자열이나 리스트를 반환하지 않습니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

객체 선택과 표현 변환을 각각 지연 단계로 유지합니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/06-contract.sh로 확인하세요.

내려받은 줄을 다시 검증한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: decode_lines(text)는 splitlines의 각 비어 있지 않은 줄을 json.loads 후 validate_row하고 리스트로 반환합니다. 빈 문자열은 [], 비어 있는 중간 줄은 ValueError입니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

빈 파일과 형식이 깨진 빈 레코드를 구분합니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/07-contract.sh로 확인하세요.

HTTP 다운로드를 완성한다 — 테스트

/root/work/test-lazy-export-lab/test_service.py에서 제공된 service.py의 다음 공개 계약을 테스트하세요: create_app(rows)는 GET /export에서 json_lines(rows, 100)을 application/x-ndjson StreamingResponse로 반환합니다. rows는 다시 순회 가능한 리스트입니다. 내부 필드가 없고 각 행의 내용과 순서를 보존해야 합니다. 정상 구현에서는 통과하고 이 계약을 어긴 구현에서는 실제 테스트 본문의 실패로 검출해야 합니다. 앞 단계 테스트를 유지하며 test_ 함수를 추가하세요.

Content-Type만 스트리밍으로 적고 내부에서는 전체를 모으지 않았는지 생성기 시험과 함께 확인합니다. 구현 파일은 수정하지 않습니다. pytest.raises로 예상 예외를 확인하고 정상 결과에는 구체적인 예상값을 단언하세요.

저장 후 bash /opt/lab/checks/test-lazy-export-lab/08-contract.sh로 확인하세요.