[Flask] Testing
정의
Flask Testing 은 앱 인스턴스를 직접 감싸 HTTP 요청을 시뮬레이션하는 workflow 입니다. app.test_client() (Werkzeug) 로 라우팅/뷰/미들웨어까지 실제 실행하고 응답을 검증합니다. HTTP 서버 없이 프로세스 내부에서 수행되어 매우 빠릅니다.
기본 사용
def test_index():
app = create_app("testing")
client = app.test_client()
resp = client.get("/")
assert resp.status_code == 200
assert resp.get_json() == {"hello": "world"}
pytest 통합
# conftest.py
import pytest
from myapp import create_app
from myapp.extensions import db
@pytest.fixture
def app():
app = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def runner(app):
return app.test_cli_runner()
# tests/test_users.py
def test_create_user(client):
resp = client.post("/users/", json={"email": "a@b.com", "password": "secret123"})
assert resp.status_code == 201
data = resp.get_json()
assert data["email"] == "a@b.com"
def test_get_user(client):
resp = client.get("/users/1")
assert resp.status_code == 200
HTTP 요청 API
# GET with query
client.get("/items/", query_string={"q": "search"})
# POST JSON
client.post("/items/", json={"name": "foo"})
# POST form
client.post("/login", data={"email": "a@b.com", "password": "secret"})
# POST multipart (파일)
from io import BytesIO
client.post(
"/upload",
data={"file": (BytesIO(b"file content"), "test.txt")},
content_type="multipart/form-data",
)
# Headers
client.get("/protected", headers={"Authorization": "Bearer token"})
# Cookies
client.set_cookie("localhost", "session", "abc")
resp = client.get("/me")
Response 검증
def test_response(client):
resp = client.get("/api")
assert resp.status_code == 200
assert resp.mimetype == "application/json"
# JSON body
data = resp.get_json()
assert data["key"] == "value"
# Raw body
assert b"success" in resp.data
text = resp.get_data(as_text=True)
# Headers
assert resp.headers["X-Custom"] == "value"
# Cookies
assert "session" in resp.headers.get("Set-Cookie", "")
Session 조작
def test_session(client):
with client.session_transaction() as sess:
sess["user_id"] = 1
resp = client.get("/me")
assert resp.status_code == 200
session_transaction context 안에서 세션을 조작하고, 이후 요청에 세션이 반영됨.
test_request_context
Request context 를 수동으로 열기 (뷰 함수 밖에서 request, url_for 등 접근):
def test_url_for(app):
with app.test_request_context("/hello?q=world"):
assert request.path == "/hello"
assert request.args["q"] == "world"
assert url_for("index") == "/"
test_cli_runner (CLI 명령 테스트)
def test_seed_cli(runner):
result = runner.invoke(args=["seed-db", "--count", "10"])
assert result.exit_code == 0
assert "Seeded 10" in result.output
DB 격리 패턴
패턴 1: fresh schema per test
@pytest.fixture
def app():
app = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.drop_all()
빠르지만 테스트 간 오염 위험 (fixture 로 매번 clear 필요).
패턴 2: Transaction rollback per test
@pytest.fixture
def app():
app = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.drop_all()
@pytest.fixture
def db_session(app):
connection = db.engine.connect()
transaction = connection.begin()
session = db.session
session.bind = connection
yield session
transaction.rollback()
connection.close()
각 테스트 후 rollback -> 완벽 격리.
패턴 3: SQLite in-memory
class TestingConfig(Config):
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
가장 빠름. 하지만 PostgreSQL-specific 기능 (JSONB, LATERAL, …) 은 테스트 불가.
패턴 4: testcontainers
from testcontainers.postgres import PostgresContainer
@pytest.fixture(scope="session")
def postgres():
with PostgresContainer("postgres:16") as p:
yield p.get_connection_url()
@pytest.fixture(scope="session")
def app(postgres):
os.environ["DATABASE_URL"] = postgres
app = create_app("testing")
...
프로덕션 DB 와 동일한 환경. Docker 필요.
Authentication 테스트
Flask-Login 사용 시:
@pytest.fixture
def logged_in_user(client, app):
with app.app_context():
user = User(email="test@example.com", password_hash=hash("secret"))
db.session.add(user)
db.session.commit()
user_id = user.id
with client.session_transaction() as sess:
sess["_user_id"] = str(user_id)
sess["_fresh"] = True
return user_id
def test_dashboard(client, logged_in_user):
resp = client.get("/dashboard")
assert resp.status_code == 200
Mocking
unittest.mock 활용:
from unittest.mock import patch
def test_external_call(client):
with patch("myapp.services.external_api.post") as mock_post:
mock_post.return_value.json.return_value = {"status": "ok"}
resp = client.post("/webhook", json={"event": "..."})
assert resp.status_code == 200
mock_post.assert_called_once()
Flask-Caching 무효화
class TestingConfig(Config):
CACHE_TYPE = "NullCache" # 캐시 안 함
또는 fixture 로 clear:
@pytest.fixture(autouse=True)
def clear_cache(app):
from myapp.extensions import cache
cache.clear()
Async view 테스트 (Flask 2.0+)
async def test_async_view(client):
resp = await client.get("/async") # httpx-like
assert resp.status_code == 200
Flask 는 async view 지원이 제한적. pytest-asyncio 조합.
Coverage
pip install pytest-cov
pytest --cov=myapp --cov-report=term-missing --cov-report=html
pyproject.toml:
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.coverage.run]
branch = true
source = ["myapp"]
omit = ["*/migrations/*", "wsgi.py"]
[tool.coverage.report]
fail_under = 80
show_missing = true
E2E 테스트 (실제 서버)
Selenium/Playwright 로 브라우저 자동화:
import pytest
from playwright.sync_api import Page
from myapp import create_app
from threading import Thread
@pytest.fixture(scope="session")
def live_server():
app = create_app("testing")
server = make_server("127.0.0.1", 5000, app)
thread = Thread(target=server.serve_forever)
thread.start()
yield "http://127.0.0.1:5000"
server.shutdown()
thread.join()
def test_home_page(page: Page, live_server):
page.goto(f"{live_server}/")
assert page.title() == "My App"
함정
WARNING
App context / request context 혼동. test_request_context 는 request 시뮬레이션, app_context 는 앱 활성화. DB 조작은 앱 컨텍스트 필요.
CAUTION
db.session.remove() 안 하면 다음 테스트로 세션 leak. teardown fixture 에서 반드시.
WARNING
CSRF 비활성화. 테스트 시 WTF_CSRF_ENABLED = False. 활성이면 매 폼 테스트마다 토큰 발급 필요.
IMPORTANT
Fixture scope. DB migrations 는 session scope, transaction rollback 은 function scope. 잘못 조합하면 매우 느림.
CAUTION
환경 변수 leak. 테스트에서 os.environ 을 조작하면 다른 테스트 영향. monkeypatch.setenv 사용.
관련 위키
- Flask - 상위 개요
- Flask App Factory - 테스트용 factory
- Flask Blueprints - Blueprint 테스트
- Flask Sessions - session_transaction
- Flask Extensions - extension 테스트
- pytest - 테스트 프레임워크
- FastAPI Testing - 대비
이 글의 용어 (7개)
- [FastAPI] Testingfastapi
- 정의 FastAPI Testing 은 endpoint 를 실제 서버 없이 직접 호출해 응답을 검증하는 workflow 입니다. Starlette 의 (내부적으로 sync) 또는 …
- [Flask] Application Factory Patternflask
- 정의 Application Factory 는 Flask 앱 인스턴스를 함수 안에서 생성 하는 패턴입니다. 모듈 스코프에 를 두는 대신 함수를 정의해 반환합니다. 이 패턴은 사실상…
- [Flask] Blueprintsflask
- 정의 Blueprint 는 Flask 앱을 여러 모듈로 나누는 표준 도구입니다. 라우트, 정적파일, 템플릿, before/after hook, error handler 를 그룹화…
- [Flask] Extensions (SQLAlchemy, Login, Migrate, WTF, ...)flask
- 정의 Flask Extension 은 코어에 없는 기능 (DB, 인증, 마이그레이션, 캐시 등) 을 표준 인터페이스로 통합하는 서드파티 패키지입니다. 패턴이 관용이며, appli…
- [Flask] Sessions & Cookiesflask
- 정의 Flask Session 은 요청 간 상태를 유지하는 방법입니다. 기본 구현은 서명된 쿠키 (secure cookie) 로 클라이언트에 저장 (server-less). ex…
- [Python] Flaskflask
- 정의 Flask 는 Armin Ronacher 가 2010년 발표한 WSGI 기반 Python 마이크로프레임워크 입니다. Pallets Projects 가 유지관리하고, 2026…
- [Python] pytest: fixture, parametrize, mockpython
- 정의 는 Python 표준 테스트 프레임워크의 사실상 표준(stdlib는 이지만 pytest가 더 강력). 단순한 함수 로 시작해서 fixture, parametrize, plu…
💬 댓글