출시·고도화 중
FastAPI 안내서 · 5/6
FastAPI 애플리케이션은 서버를 띄우지 않고도 테스트할 수 있습니다. 이 장에서는 TestClient와 pytest로 엔드포인트를 테스트하고, 의존성 재정의로 데이터베이스나 설정을 테스트용으로 바꾸는 방법, 비동기 테스트와 커버리지 측정을 다룹니다.
TestClient는 HTTPX를 바탕으로 만들어져 있습니다. fastapi[standard]로 설치했다면 HTTPX가 이미 들어 있으므로 pytest만 추가합니다.
pip install pytest테스트 파일은 tests/test_*.py 형태로 두고, 함수 이름은 test_로 시작합니다. 프로젝트 루트에서 pytest를 실행하면 테스트를 자동으로 찾아 실행합니다. app 패키지를 가져올 수 있도록 tests 폴더에도 빈 __init__.py를 두거나 pyproject.toml의 pytest 설정에 pythonpath = ["."]를 지정합니다.
TestClient에 앱을 넘기면 requests와 비슷한 방식으로 요청을 보내고 응답을 확인할 수 있습니다. 테스트 함수는 일반 def로 작성합니다.
# tests/test_main.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello, world!"}
def test_read_item_validation_error():
response = client.get("/items/not-a-number")
assert response.status_code == 422
def test_create_item():
response = client.post("/items", json={"name": "Pen", "price": 1.5})
assert response.status_code == 201
assert response.json()["name"] == "Pen"헤더는 headers={"X-Token": "..."}, 쿼리 매개변수는 params={"q": "pen"}로 넘깁니다. 검증 실패가 422로 돌아오는지 확인하는 테스트도 함께 두면 API 계약이 바뀌는 것을 일찍 발견할 수 있습니다.
실제 데이터베이스나 외부 서비스 대신 테스트용 구현을 쓰려면 app.dependency_overrides에 원래 의존성과 대체 함수를 짝지어 넣습니다. 아래는 메모리 SQLite 데이터베이스로 세션 의존성을 바꾸는 예입니다.
# tests/test_heroes.py
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel.pool import StaticPool
from app.db import get_session
from app.main import app
@pytest.fixture(name="session")
def session_fixture():
engine = create_engine(
"sqlite://",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
yield session
@pytest.fixture(name="client")
def client_fixture(session: Session):
app.dependency_overrides[get_session] = lambda: session
client = TestClient(app)
yield client
app.dependency_overrides.clear()
def test_create_hero(client: TestClient):
response = client.post(
"/heroes", json={"name": "Deadpond", "secret_name": "Dive Wilson"}
)
data = response.json()
assert response.status_code == 200
assert data["name"] == "Deadpond"
assert "secret_name" not in dataStaticPool은 메모리 데이터베이스가 여러 연결 사이에서 사라지지 않도록 하나의 연결을 계속 쓰게 합니다. 테스트가 끝나면 dependency_overrides.clear()로 원래 상태로 되돌려 다른 테스트에 영향을 주지 않게 합니다. 설정 의존성 get_settings도 같은 방식으로 바꿀 수 있습니다.
TestClient를 with 문으로 쓰면 앱의 lifespan 시작 코드와 종료 코드가 테스트 앞뒤로 실행됩니다. 시작할 때 모델을 불러오거나 연결을 여는 앱이라면 이 방식을 씁니다.
from fastapi.testclient import TestClient
from app.main import app
def test_with_lifespan():
with TestClient(app) as client:
response = client.get("/health")
assert response.status_code == 200테스트 안에서 비동기 데이터베이스 호출처럼 await가 필요한 코드를 함께 실행해야 한다면 HTTPX의 AsyncClient와 ASGITransport를 씁니다. 테스트를 비동기로 실행하는 데에는 AnyIO의 pytest 플러그인을 사용할 수 있습니다.
import pytest
from httpx import ASGITransport, AsyncClient
from app.main import app
@pytest.mark.anyio
async def test_root_async():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
response = await ac.get("/")
assert response.status_code == 200pytest # 모든 테스트
pytest tests/test_heroes.py -v # 한 파일만 자세히
pytest -k create # 이름에 create가 들어간 테스트만
pip install pytest-cov
pytest --cov=app # 커버리지 측정TestClient(app)로 서버 없이 요청을 보내고 상태 코드와 JSON을 확인합니다.app.dependency_overrides로 데이터베이스, 설정, 인증 같은 의존성을 테스트용으로 바꿉니다.lifespan이 필요하면 with TestClient(app) as client:를 씁니다.AsyncClient와 ASGITransport를 사용합니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.