Flask 앱 테스트하기 — pytest와 테스트 클라이언트

Flask 앱 테스트하기 — pytest와 테스트 클라이언트

Flask 앱을 만들면 "문서대로 잘 동작할까?"를 확인하고 싶어져요. Flask는 이를 위해 애플리케이션 테스트 유틸리티를 제공해요. 실제 서버를 띄우지 않고도 요청을 보내고 응답을 검사할 수 있는 테스트 클라이언트를 쓰는 것이 핵심이에요.

출처: Testing Flask Applications — Flask Documentation

Flask는 애플리케이션을 테스트하는 유틸리티를 제공해요. 여기서는 테스트 셋업과 실행에 pytest 프레임워크를 써요.

$ pip install pytest

Fixtures

pytest의 fixture는 테스트 사이에서 재사용 가능한 코드 조각을 미리 만들어 두는 기능이에요. 앱·테스트 클라이언트·CLI 러너를 위한 fixture가 아래와 같이 tests/conftest.py에 들어갈 수 있어요.

애플리케이션 팩토리(app factory)를 쓴다면 app fixture를 정의해 앱 인스턴스를 만들고 설정해요. yield 앞뒤에 코드를 넣어 데이터베이스 생성·정리 같은 다른 리소스의 셋업·티어다운을 처리할 수 있어요.

import pytest
from my_project import create_app

@pytest.fixture()
def app():
    app = create_app()
    app.config.update({
        "TESTING": True,
    })
    # other setup can go here
    yield app
    # clean up / reset resources here

@pytest.fixture()
def client(app):
    return app.test_client()

@pytest.fixture()
def runner(app):
    return app.test_cli_runner()

테스트 클라이언트로 요청 보내기

테스트 클라이언트는 실서버를 띄우지 않고 앱에 요청을 보내요. Flask의 클라이언트는 Werkzeug의 클라이언트를 확장한 것이에요. client.get()client.post()처럼 흔한 HTTP 요청 메서드에 대응하는 메서드가 있어요. 보통 path, query_string, headers, data 또는 json을 써요.

def test_request_example(client):
    response = client.get("/posts")
    assert b"<h2>Hello, World!</h2>" in response.data

응답은 TestResponse 객체로, 응답 객체의 모든 일반 프로퍼티를 가져요. 대개 response.data(뷰가 돌려준 바이트)를 살펴봐요. 텍스트가 필요하면 response.textresponse.get_data(as_text=True)를 써요.

쿼리 문자열을 설정하려면 딕셔너리를 query_string={"key":"value",...}로, 요청 헤더는 headers={}로 전달해요.

POST/PUT 요청 본문을 보내려면 data에 값을 전달해요. 원시 바이트를 넘기면 그대로 본문이 되고, 보통은 딕셔너리를 넘겨 폼 데이터를 설정해요. 값이 바이트 읽기 모드("rb")로 연 파일 객체라면 업로드 파일로 취급돼요. 파일 이름·콘텐츠 타입을 바꾸려면 (file, filename, content_type) 튜플을 전달해요.

애플리케이션·요청 컨텍스트

데이터베이스 확장은 대개 활성 앱 컨텍스트가 필요해요. with app.app_context():로 앱 컨텍스트를 밀어 넣으면 돼요.

def test_db_post_model(app):
    with app.app_context():
        post = db.session.query(Post).get(1)

요청 컨텍스트가 필요하면 with app.test_request_context():를 써요. 테스트 클라이언트의 요청 메서드와 같은 인자를 받아요.

def test_validate_user_edit(app):
    with app.test_request_context("/user/2/edit", method="POST", data={"name": ""}):
        # call a function that accesses `request`
        messages = validate_edit_user()
        assert messages["name"][0] == "Name cannot be empty."

테스트 요청 컨텍스트를 만드는 것은 Flask의 디스패칭 코드를 실행하지 않아서 before_request 함수가 호출되지 않아요. 그런 것들이 필요하면 보통 완전한 요청을 만드는 게 더 좋아요. 그래도 수동으로 호출할 수는 있어요.

정리하면, Flask 테스트는 app.test_client()로 실서버 없이 요청을 보내고, app.app_context()·app.test_request_context()로 컨텍스트가 필요한 코드를 검증하는 방식이에요. pytest의 fixture로 앱·클라이언트·러너를 재사용하면 깔끔하게 구성돼요.

더 알아보기