3줄 요약 스냅샷을 처음 만들 때도 -n auto 를 붙여 시간을 아끼는 것이 습관이 돼 있었습니다. syrupy 6.1.1과 pytest-xdist 3.8.0 조합에서 20개를 만들면 4워커는 15~16개, 8워커는 최악 2개만 파일에 남았습니다. 그런데 화면에는 20 snapshots generated 가 찍히고 종료 코드는 0이었습니다. 6.1.1에 새로 들어온 --snapshot-file-lock 을 붙이면 10회 실행에서 전부 20개가 남았고, 8워커 기준 실행 시간 차이는 0.001초였습니다.
3줄 요약 pytest 9.1.0(2026-06-13 릴리스)부터 caplog 가 propagate=False 로거에도 핸들러를 직접 답니다. 그동안 캡처를 포기하고 우회책을 넣어 둔 스위트가 영향을 받습니다. monkeypatch.setattr(log, "propagate", True) 우회책을 쓰던 테스트는 같은 로그 한 줄에서 레코드가 1개에서 2개로 늘었고, 같은 스위트의 --log-file 출력은 0줄에서 10줄(440B)이 됐습니다. 업그레이드 전에 propagate 를 손대는 자리부터 찾아 지우십시오. 로거 5,000개 환경에서는 스캔 비용도 300테스트당 0.16초에서 0.53초로 붙습니다.
3줄 요약 합성 스위트(테스트 200개)에서 맵을 만드는 값은 전체 실행 대비 +19.7%이고, 잎 모듈 하나를 고쳤을 때 남는 실행은 25.1%입니다. 같은 맵이 conftest 변경·데이터 파일 변경·새 테스트 파일·아무 테스트도 안 닿은 파일 네 자리에서 0개 를 고릅니다. 데이터 파일 한 줄을 바꾼 경우 실제로는 40개가 깨졌는데 맵은 "돌릴 것 없음"을 냈습니다. 맵에 없는 경로가 변경 목록에 하나라도 섞이면 전체 실행으로 되돌리는 20줄짜리 가드를 먼저 붙이고, 그다음에 선택 실행을 1차 피드백 자리에만 두는 순서입니다.
3줄 요약 먼저 확인할 것은 --strict-markers 가 막아 주는 범위입니다. 등록되지 않은 데코레이터는 수집 에러(exit 2)로 잡지만, -m 명령줄 표현식은 아예 보지 않습니다. 그다음 할 일은 -m 표현식을 검사하는 conftest.py 가드입니다. 20줄짜리 훅 하나로 실측한 오타 3종이 전부 exit 4에서 멈췄습니다. 여기서 멈추십시오. 수집 비용 차이는 2,400개 기준 0.54초뿐이고, 그 숫자 때문에 스위트를 디렉터리로 갈아엎을 이유는 없습니다.
3줄 요약 예제 기반 테스트 12개가 모두 통과하고 coverage.py 분기 커버리지가 100%인 모듈에 속성 기반 테스트 4개를 붙였더니 3개가 즉시 반례를 냈습니다. 반례 탐지에 필요한 max_examples 는 25면 충분했고(세 속성 모두 시드 30개 중 30개 탐지), 비용은 속성 4개 기준 0.311초로 예제 12개의 0.132초 대비 0.179초 증가였습니다. 오늘 붙일 계획이라면 속성을 늘리기 전에 CI 환경변수가 있을 때 기본값 다섯 개가 한꺼번에 바뀐다는 것부터 확인하시면 됩니다.
3줄 요약 멈춘 테스트를 끊는 방법은 pytest-timeout의 signal · thread 와 pytest 내장 faulthandler 세 갈래인데, pytest-timeout 공식 문서는 thread를 "가장 확실한 방법"이라고 적고 있습니다. 행 11종을 상한 2초로 돌려 보니 signal 9/11, thread 10/11, faulthandler(exit=true) 11/11이었고 signal과 thread가 놓친 항목이 하나도 겹치지 않았습니다. 정규식 백트래킹은 thread만 놓쳤고, SIGALRM을 건드린 테스트는 signal만 놓쳤습니다. xdist로 병렬 실행 중이면 thread , 단일 프로세스면 signal 을 기본값으로 두고 느린 테스트만 @pytest.mark.timeout 으로 예외 처리하시면 됩니다.
3줄 요약 두 라이브러리는 고정하는 함수 목록이 다릅니다. freezegun은 time.monotonic() 까지 멈추고, time-machine은 그것만 실제 시각으로 남깁니다. 같은 datetime(2026, 3, 1, 12, 0) 을 넣어도 datetime.now() 가 12:00과 21:00으로 갈립니다. 진입·해제 비용은 923.63us 대 8.52us로 108.4배 차이입니다. 옮길 계획이 있다면 python -m time_machine migrate 를 돌리기 전에 conftest에 naive_mode 부터 정해 두시면 됩니다.
3줄 요약 pytest-randomly 5.0.0이 pytest_collection_modifyitems 를 평범한 tryfirst 에서 훅 래퍼로 바꿔, 다른 플러그인보다 항상 먼저 셔플하도록 만들었습니다. 그룹 정렬 플러그인이 없으면 같은 시드가 4.1.0과 똑같은 순서를 냅니다. 있으면 40칸이 전부 달라지고, 그룹 경계를 넘는 순서 의존성 탐지율이 시드 60개 기준 56.7%에서 0%로 떨어졌습니다. 정렬 플러그인을 함께 쓰신다면 셔플 잡을 -p no:그룹플러그인 조합으로 따로 하나 더 돌리시면 됩니다.
3줄 요약 줄 커버리지 100%, 테스트 11개 전부 통과인 모듈에 mutmut 3.7.0을 걸었더니 뮤턴트 99개 중 35개가 살아남았습니다. 뮤테이션 스코어 64.6%입니다. 어서션을 정확한 값으로 바꾸는 네 라운드로 98.0%까지 올렸고, 그 과정에서 무료배송 경계값과 등급 문자열 오타를 조용히 통과시키는 설계 결함을 찾았습니다. 커버리지 지표를 뮤테이션으로 바꾸지 말고, 결제·정산처럼 금액을 계산하는 모듈 하나에만 걸어 mutmut export-cicd-stats 로 생존 수를 세어 보시면 됩니다.
3줄 요약 같은 이름의 테스트 파일이 두 개 생기면 그 파일만 실패하는 것이 아니라 수집 자체가 중단됩니다. pytest 9.1.1로 재현하니 파일 3개에 오류 2건, exit 2, 실행된 테스트 0건이었습니다. 처방은 __init__.py 와 --import-mode=importlib 두 가지이고 수집 시간 차이는 없었습니다(테스트 300개 기준 0.03초 대 0.04초). 갈리는 지점은 테스트 디렉터리 안에 헬퍼 모듈을 두고 있는지입니다. 지금 저장소에서 중복 파일명 탐지 스크립트를 한 번 돌려 보시면 됩니다. 아직 안 터졌더라도 파일 하나만 더 추가되면 그날 CI가 멈춥니다.
3줄 요약 테스트를 잡으로 쪼개면 파이프라인이 빨라지지만, 어느 지점부터는 벽시계가 거의 안 줄고 청구 분만 늘어납니다. 200개·232초짜리 스위트를 실측한 결과 잡 4개에서 벽시계 120초, 8개에서 90초였고 청구는 8분에서 16분으로 정확히 2배였습니다. 잡 고정비를 60초로 잡았을 때입니다. 잡 수 상한을 전체 테스트 시간 ÷ 잡 고정비 로 먼저 계산하시고, 그 값을 넘기지 않는 선에서 --splitting-algorithm least_duration 을 붙이시면 됩니다.
3줄 요약 Allure Report 2의 트렌드와 History 탭은 리포트를 만들 때 나오는 history 폴더를 다음 실행의 결과 디렉터리로 되돌려 놓아야 이어집니다. CI에서 이 한 줄이 빠지면 리포트는 정상으로 보이는데 트렌드만 매번 1칸입니다. 26회를 쌓은 상태에서 복사를 한 번 빠뜨려 보니 트렌드 20칸이 1칸으로, statistic.total 이 26에서 1로 돌아갔습니다. 되돌릴 방법은 이전 리포트 아티팩트를 다시 찾는 것뿐입니다. CI 캐시나 GitHub Pages 브랜치로 history 를 영속화하고, flaky율은 20개 상한이 걸린 items 가 아니라 무한 누적되는 statistic 으로 계산하시면 됩니다.
3줄 요약 pytest-rerunfailures로 재시도를 걸어 CI 실패 알림을 줄였는데, "얼마나 자주 재시도됐는지"는 JUnit XML 어디에도 남지 않았습니다. 원인은 pytest의 리포트 훅이 재시도 과정을 표준 리포트 형식에 담지 않기 때문이며, pytest-reportlog의 --report-log 만 시도별 기록을 JSON 줄로 남깁니다. 오늘 CI 설정에 --report-log=results.jsonl 한 줄을 추가하고, 글 안의 스크립트로 재시도율을 GitHub Actions 요약에 올려보시면 됩니다.
3줄 요약 pytest-xdist는 워커마다 따로 수집하고 따로 실행하기 때문에, scope="session" 픽스처가 워커 수만큼 반복 실행됩니다. 테스트 24개·준비 1.5초 기준으로 실측하니 실행 시간은 -n 0 1.62초에서 -n 8 1.85초로 거의 같았고, 실제 피해는 로그인·시드·외부 API 호출이 8번 일어나는 부작용이었습니다. 공식 FileLock 레시피로 준비를 1회로 줄일 수 있지만 yield teardown을 붙이면 준비가 다시 3번 돌았으니, teardown 없는 return 형태로 먼저 붙여 보시면 됩니다.