pytest-test-categories v1.1.0 발표: Google 테스트 철학을 파이썬에 도입합니다
Source: Dev.to
대부분의 테스트 스위트가 가진 문제점
솔직히 말해 보세요 – 아래 항목 중 얼마나 많은 것이 여러분의 코드베이스에 해당하나요?
- ⏱️ 테스트에 시간 예산이 없어 CI 파이프라인이 느려짐
- 🎲 네트워크 타임아웃, 레이스 컨디션, 공유 상태 등으로 인한 불안정한 테스트
- 🔺 너무 많은 느린 통합 테스트로 인해 역전된 테스트 피라미드
- 🚫 유닛, 통합, 시스템 테스트 사이에 강제된 경계가 없음
이 중 하나라도 끄덕였다면, 혼자가 아닙니다. 이것이 파이썬 프로젝트에서 가장 흔한 테스트 안티패턴입니다.
소개합니다 pytest-test-categories v1.1.0
Google의 검증된 테스트 철학(Software Engineering at Google)을 파이썬에 가져다 주는 pytest 플러그인입니다.
pip install pytest-test-categories
1. 명확한 리소스 제한으로 테스트를 크기로 구분하기
import pytest
import requests
@pytest.mark.small
def test_pure_function():
"""Must complete in 0"""
@pytest.mark.large
def test_external_api():
"""Full network access, up to 15 minutes"""
response = requests.get("https://api.example.com")
assert response.ok
@pytest.mark.xlarge
def test_extended_e2e():
"""Full access, up to 15 minutes, for extensive E2E tests"""
# Long‑running end‑to‑end workflow
pass
2. 헤르메틱(격리) 강제
======================================================================
[TC001] 네트워크 위반
======================================================================
Category: SMALL
무슨 일이 발생했나요:
SMALL 테스트가 api.example.com:443에 네트워크 연결을 시도했습니다
해결 방법 (하나 선택):
• responses, httpretty, 또는 respx를 사용해 네트워크 호출을 모킹하세요
• 의존성 주입을 사용해 가짜 HTTP 클라이언트를 제공하세요
• 테스트 카테고리를 @pytest.mark.medium 로 변경하세요
======================================================================
3. 테스트 피라미드 검증
======================== 테스트 크기 분포 ========================
Small: 120 테스트 (80.0%) - 목표: 80% ✓
Medium: 22 테스트 (14.7%) - 목표: 15% ✓
Large: 8 테스트 ( 5.3%) - 목표: 5% ✓
========================================================================
‘allow network’ 우회 옵션 없음
플러그인은 의도적으로 @pytest.mark.allow_network 마커를 제공하지 않습니다. 이런 마커를 추가하면 목적이 무색해집니다:
# This defeats the entire purpose
@pytest.mark.small
@pytest.mark.allow_network # ❌ This marker doesn't exist!
def test_api():
requests.get("https://api.example.com") # Still flaky!
대신 적절한 카테고리를 사용하세요:
@pytest.mark.medium # ✓ Honest about what the test does
def test_api():
requests.get("https://api.example.com")
테스트 크기별 리소스 매트릭스
| 리소스 | Small | Medium | Large | XLarge |
|---|---|---|---|---|
| 시간 | 1 초 | 5 분 | 15 분 | 15 분 |
| 네트워크 | ❌ 차단 | Localhost ✓ | ✓ 허용 | ✓ 허용 |
| 파일시스템 | ❌ 차단 | ✓ 허용 | ✓ 허용 | ✓ 허용 |
| 데이터베이스 | ❌ 차단 | ✓ 허용 | ✓ 허용 | ✓ 허용 |
| 서브프로세스 | ❌ 차단 | ✓ 허용 | ✓ 허용 | ✓ 허용 |
| Sleep | ❌ 차단 | ✓ 허용 | ✓ 허용 | ✓ 허용 |
작은 테스트에서 모킹하기
작은 테스트는 responses, respx, pytest-mock, pyfakefs, VCR.py 같은 모킹 라이브러리를 사용해도 실제 리소스에 도달하기 전에 라이브러리 레이어에서 가로채므로 위반이 발생하지 않습니다:
import pytest
import responses
import requests
@pytest.mark.small
@responses.activate
def test_api_with_mock():
"""This is hermetic – no real network call is made."""
responses.add(
responses.GET,
"https://api.example.com/users",
json={"users": []},
status=200,
)
response = requests.get("https://api.example.com/users")
assert response.json() == {"users": []}
파일시스템 작업을 작은 테스트에서 수행할 때는 pyfakefs 혹은 io.StringIO / io.BytesIO 같은 메모리 객체를 사용하세요.
점진적 강제 적용 롤아웃
첫날부터 엄격하게 적용할 필요는 없습니다:
# pyproject.toml
# Week 1: 탐색 – 어떤 것이 실패할지 확인
[tool.pytest.ini_options]
test_categories_enforcement = "off"
# Week 2‑4: 마이그레이션 – 위반을 점진적으로 수정
test_categories_enforcement = "warn"
# Week 5+: 강제 – 위반 시 빌드 실패
test_categories_enforcement = "strict"
병렬 실행 지원
pytest-xdist를 완전 지원하면서 카테고리별 테스트를 병렬로 실행하세요:
pytest -n auto
분산 통계는 워커 간에 올바르게 집계되며, 타이머 격리로 레이스 컨디션을 방지합니다.
머신 가독성 보고서 내보내기
대시보드와 CI 연동을 위한 JSON 보고서를 생성합니다:
pytest --test-size-report=json --test-size-report-file=report.json
{
"summary": {
"total_tests": 150,
"distribution": {
"small": { "count": 120, "percentage": 80.0 },
"medium": { "count": 22, "percentage": 14.67 },
"large": { "count": 8, "percentage": 5.33 }
},
"violations": {
"timing": 0,
"hermeticity": {
"network": 0,
"filesystem": 0,
"subprocess": 0,
"database": 0,
"sleep": 0,
"total": 0
}
}
}
}
설치 및 빠른 시작
pip install pytest-test-categories
pyproject.toml에 강제 적용을 활성화하세요:
[tool.pytest.ini_options]
test_categories_enforcement = "warn"
리소스
- 📦 PyPI
- 📖 Documentation