stdlib 에 unittest 있어. pytest 가 third-party 라이브러리, 같은 일을 훨씬 적은 ceremony 로. TestCase 베이스 클래스 X, self.assertEqual X — test_ 로 시작하는 함수 + 그냥 assert. 실패 시 출력 풍부 + 읽기 좋음. 현대 Python 프로젝트 (Pippa 포함) 거의 항상 pytest.
기본 모양
test_foo.py 이름의 파일 + test_ 로 시작하는 함수. 각 함수 안에 그냥 assert. 프로젝트 디렉토리에서 pytest 실행 + 모두 발견 + 실행. 특정 테스트엔 pytest path/to/file.py::test_name.
Fixture — setup/teardown 우아하게
@pytest.fixture 가 setup 함수 선언. fixture 이름의 인자 받는 테스트가 그 반환값 받음. fixture 가 setup-그-다음-teardown 모양엔 return 대신 yield 가능. fixture 가 scope (scope="function", "module", "session") 기반 똑똑한 캐싱으로 테스트 간 재사용.
parametrize — 같은 테스트, 많은 입력
@pytest.mark.parametrize("a,b,expected", [(1, 2, 3), (5, 5, 10)]) 가 row 당 한 번 테스트 실행. 각 row 가 출력에서 별 테스트 (test_add[1-2-3]). "같은 테스트 다른 입력 5 번 복붙" 을 한 데코레이터로.
Async 테스트 — pytest-asyncio
Async 테스트 함수엔 pytest-asyncio 플러그인 (또는 pytest-anyio) 필요. 설치 후 @pytest.mark.asyncio 로 표시 (또는 auto 모드 설정). Pippa 의 테스트가 이거 사용 — 코드베이스의 모든 async 함수가 진짜 async path 통한 테스트 가짐.
import pytest
import tempfile
from pathlib import Path
@pytest.fixture
def temp_file():
# Setup
f = tempfile.NamedTemporaryFile(delete=False, suffix=".txt")
f.write(b"hello")
f.close()
path = Path(f.name)
yield path # 테스트에 제공
# Teardown — 테스트 후 실행, 실패해도
path.unlink(missing_ok=True)
def test_read(temp_file):
content = temp_file.read_text()
assert content == "hello"
# 이름으로 fixture — pytest 가 파라미터를 fixture 매치
parametrize — 많은 입력, 한 테스트·python
import pytest
def divide(a, b):
return a / b
@pytest.mark.parametrize("a,b,expected", [
(10, 2, 5),
(9, 3, 3),
(1, 4, 0.25),
(-6, 2, -3),
])
def test_divide(a, b, expected):
assert divide(a, b) == expected
# 각 row 가 별 테스트로 실행:
# test_divide[10-2-5] PASSED
# test_divide[9-3-3] PASSED
# test_divide[1-4-0.25] PASSED
# test_divide[-6-2--3] PASSED
# 5 번째 입력 추가가 한 줄
예외 테스트 — pytest.raises·python
import pytest
def divide(a, b):
if b == 0:
raise ZeroDivisionError("can't")
return a / b
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError, match="can't"):
divide(10, 0)
# raises 가 예외 검사에도
def test_with_inspection():
with pytest.raises(ValueError) as exc_info:
raise ValueError("specific message")
assert "specific" in str(exc_info.value)
Async 테스트 — pytest-asyncio·python
# pip install pytest-asyncio
#
# pyproject.toml 에:
# [tool.pytest.ini_options]
# asyncio_mode = "auto" # async 테스트 자동 검출
import pytest
import asyncio
async def fetch(name):
await asyncio.sleep(0.01)
return f"got {name}"
@pytest.mark.asyncio # 또는 auto 모드가 잡음
async def test_fetch():
result = await fetch("foo")
assert result == "got foo"
@pytest.mark.asyncio
async def test_concurrent():
a, b = await asyncio.gather(fetch("a"), fetch("b"))
assert a == "got a"
assert b == "got b"
작은 calculator 모듈 — add, subtract, multiply, divide (b=0 면 ZeroDivisionError). 그 다음 test_calculator.py — (a) 각각의 기본 테스트, (b) add/subtract/multiply 에 각 최소 5 입력 조합 hit 하는 parametrized 테스트, (c) divide-by-zero 의 pytest.raises 테스트, (d) 100 random 숫자 페어 list 미리 빌드하는 fixture + 그것들에 4 연산 다 실행하는 테스트.
Progress
Progress is local-only — sign in to sync across devices.