LabHub
배우기 러닝패스 코스

失敗したのに終了コードは 0 だった

失敗をテストとして固定する

LabHub 에서 이어서 보기

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

목표

결함이 있는 로그 회전 도구를 pytest 로 재현하고 고친다 — 순수 함수 시험, tmp_path, 실패하는 시험 먼저, parametrize, capsys, monkeypatch, JUnit 보고서.

왜 중요한가

고쳤다는 말은 증거가 아니다. 버그를 재현하는 시험은 고치기 전에는 실패하고 고친 뒤에는 통과하며, 그 뒤로는 같은 버그가 돌아오는 것을 막는다. 파일·시간·출력에 의존하는 운영 도구는 tmp_path·monkeypatch·capsys 로 그 의존을 시험 안에 가둔다. 이 실습의 채점기는 여러분의 시험 파일을 원본 결함 코드에도 대고 돌려, 시험이 진짜로 버그를 잡는지 양방향으로 확인한다.

재료: /opt/fixtures/pyops/buggy/rotate.pyrotate.py DIR --keep N [--dry-run] [--older-than-days D]. plan(files, keep) 은 지울 파일 목록을 돌려주는 순수 함수이고, main(argv) 는 정수를 돌려준다. 결함 두 개: (1) keep 보다 하나 더 남긴다, (2) --dry-run 인데 지운다.

단계

  1. mkdir -p /root/pyops/test && cp /opt/fixtures/pyops/buggy/rotate.py /root/pyops/test/ 로 복사하고, /root/pyops/test/test_rotate.pyplan() 을 직접 부르는 시험 하나를 쓴다. keep 이 파일 수보다 크면 빈 목록이어야 한다. pytest -q 가 통과한다.
  2. tmp_path 에 .log 파일 5개를 만들고 rotate.main([str(tmp_path), "--keep", "3"]) 뒤 3개가 남는지 확인하는 시험을 쓴다. 함수 이름에 keep 이 들어가야 한다. 이 시험은 지금 코드에서 실패해야 한다(재현).
  3. rotate.py 의 plan() 을 고쳐 keep 개만 남게 한다. pytest -q 가 전부 통과한다.
  4. tmp_path 에 파일 2개를 만들고 --keep 0 --dry-run 뒤에도 2개가 그대로인지 확인하는 시험을 쓴다(함수 이름에 dry 포함). 원본 코드에서는 실패해야 하고, rotate.py 를 고쳐 통과시킨다.
  5. keep 을 0·1·3·10 으로 바꿔 가며 남는 파일 수가 min(5, keep) 인지 확인하는 시험을 @pytest.mark.parametrize 로 쓴다. pytest --collect-only -q[ 가 붙은 ID 가 4개 이상 보여야 한다.
  6. capsys 로 delete <파일이름> 이 표준 출력에 찍히는지 확인하는 시험을 쓴다(함수 이름에 output 포함, capsys 사용).
  7. monkeypatch 로 rotate.time.time 을 고정한 뒤 --older-than-days 7 이 8일 된 파일만 지우고 하루 된 파일은 남기는지 확인하는 시험을 쓴다(함수 이름에 older 포함, monkeypatch 사용). 파일의 수정 시각은 os.utime 으로 맞춘다.
  8. pytest -q --junitxml=/root/pyops/test/report.xml 로 보고서를 남긴다. 시험이 7개 이상이고 failures 와 errors 가 0 이어야 한다.

참고

순수 함수부터 시험한다

rotate.py 를 /root/pyops/test/ 로 복사하고 /root/pyops/test/test_rotate.py 에 plan() 을 직접 부르는 시험 하나를 쓴다. keep 이 파일 수보다 크면 빈 목록이어야 한다. pytest -q 가 통과한다.

import rotate 뒤 rotate.plan(["a.log", "b.log"], keep=10) == [] 을 assert 하면 됩니다. 시험 함수 이름은 test_ 로 시작해야 pytest 가 찾습니다.

실패하는 시험으로 재현한다

tmp_path 에 .log 5개를 만들고 rotate.main([str(tmp_path), "--keep", "3"]) 뒤 3개가 남는지 확인하는 시험(이름에 keep 포함)을 쓴다. 지금 코드에서는 실패해야 한다.

(tmp_path / f"app-{i}.log").write_text("x") 로 만들고 list(tmp_path.glob("*.log")) 의 길이를 봅니다. 결함 때문에 4개가 남아 시험이 빨개지는 것이 정상입니다.

고치고 초록을 확인한다

rotate.py 의 plan() 을 고쳐 keep 개만 남게 한다. pytest -q 가 전부 통과한다.

슬라이스 끝이 len(files) - keep 이어야 합니다. 고친 뒤 2단계 시험이 통과하고 1단계 시험도 여전히 통과해야 합니다.

두 번째 결함도 같은 순서로

tmp_path 에 파일 2개를 만들고 --keep 0 --dry-run 뒤에도 2개가 그대로인지 확인하는 시험(이름에 dry 포함)을 쓴다. 원본 코드에서 실패해야 하고, rotate.py 를 고쳐 통과시킨다.

main() 이 os.remove 를 부르기 전에 args.dry_run 을 봐야 합니다. 출력(delete ...)은 dry-run 에서도 그대로 찍는 것이 관례입니다.

입력 여러 벌을 한 시험으로

keep 을 0·1·3·10 으로 바꿔 남는 파일 수가 min(5, keep) 인지 확인하는 시험을 @pytest.mark.parametrize 로 쓴다. pytest --collect-only -q 에 [ 가 붙은 ID 가 4개 이상 나온다.

@pytest.mark.parametrize("keep", [0, 1, 3, 10]) 을 함수 위에 붙이고 인자로 keep 을 받습니다. ID 는 test_이름[0], test_이름[1] 처럼 붙습니다.

출력도 계약이다

capsys 로 delete <파일이름> 이 표준 출력에 찍히는지 확인하는 시험(이름에 output 포함, capsys 사용)을 쓴다.

함수 인자에 capsys 를 넣고 rotate.main(...) 뒤 capsys.readouterr().out 에 "delete app-0.log" 가 있는지 assert 합니다. 남긴 파일 이름은 없어야 합니다.

시간을 갈아 끼운다

monkeypatch 로 rotate.time.time 을 고정한 뒤 --older-than-days 7 이 8일 된 파일만 지우고 하루 된 파일은 남기는지 확인하는 시험(이름에 older 포함, monkeypatch 사용)을 쓴다.

monkeypatch.setattr(rotate.time, "time", lambda: FIXED) 로 고정하고 os.utime(path, (t, t)) 로 파일 시각을 FIXED - 886400, FIXED - 186400 으로 맞춥니다.

기계가 읽을 보고서

pytest -q --junitxml=/root/pyops/test/report.xml 로 보고서를 남긴다. 시험이 7개 이상이고 failures·errors 가 0 이어야 한다.

--junitxml 은 testsuite 요소에 tests·failures·errors 속성을 남깁니다. 실패가 있으면 종료 코드도 1 입니다.