Skip to content

43. Testing


1. Testing

  • Starlette 덕분에 FastAPI 애플리케이션 테스트가 쉬워졌다.
  • Requests 기반이므로 매우 직관적이다.
  • 이를 통해 FastAPI로 pytest를 직접 사용할 수 있다.


2. Using TestClient

  • TestClient를 임포트한다.
  • FastAPI에 전달하는 TestClient를 생성한다.
  • test_로 시작하는 이름으로 함수를 만드는데, 이것은 표준 pytest 규칙이다.
  • requests와 동일한 방식으로 TestClient 객체를 사용한다.
  • 표준 Python 표현식으로 간단한 assert 문을 작성한다.


from fastapi import FastAPI, responses
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/")
async def read_main():
    return {"msg": "Hello World"}

client = TestClient(app)

def test_read_main():
    response = client.get("/")

    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}


Note

  • 테스트 함수는 async def가 아닌 일반적인 def이다. 그리고 클라이언트에 대한 호출도 await을 사용하지 않는 일반적인 호출이다.


3. Separating tests

  • 실제 애플리케이션에서는 다른 파일에 테스트할 것이 있을 수 있다.
  • 그리고 FastAPI 애플리케이션은 여러 파일/모듈 등으로 구성될 수도 있다.


1) FastAPI app file

  • FastAPI app이 있는 main.py 파일이 있다고 가정해 보자.


from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_main():
    return {"msg": "Hello World"}


2) Testing file

  • 그런 다음 테스트로 test_main.py 파일을 만들고 기본 모듈인 main.py에서 app을 가져올 수 있다.


from fastapi.testclient import TestClient
from .main import app

client = TestClient(app)

def test_read_main():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"msg": "Hello World"}


4. Testing: extended example

  • 위의 예제에 세부 사항을 추가하여, 다른 부분을 테스트하는 방법을 살펴보자.


1) Extended FastAPI app file

  • FastAPI app이 있는 main_b.py 파일이 있다고 가정해 보자.
  • 그리고 이 파일에는 에러를 반환할 수 있는 GET operation이 있다.
  • 또한 여러 에러를 반환할 수 있는 POST operation이 있다.
  • 이 두 path operations 모두 X-Token header가 필요하다.


from typing import Optional
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel

app = FastAPI()

fake_secret_token = "coneofsilence"

fake_db = {
    "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"},
    "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"},
}

class Item(BaseModel):
    id: str
    title: str
    description: Optional[str] = None

@app.get("/items/{item_id}", response_model=Item)
async def read_main(item_id: str, x_token: str = Header(...)):
    if x_token != fake_secret_token:
        raise HTTPException(status_code=400, detail="Invalid X-Token header")

    if item_id not in fake_db:
        raise HTTPException(status_code=404, detail="Item not found")

    return fake_db[item_id]

@app.post("/items/", response_model=Item)
async def create_item(item: Item, x_token: str = Header(...)):
    if x_token != fake_secret_token:
        raise HTTPException(status_code=400, detail="Invalid X-Token header")

    if item.id in fake_db:
        raise HTTPException(status_code=400, detail="Item already exists")

    fake_db[item.id] = item

    return item


2) Extended testing file

  • 그런 다음 확장 테스트를 통해 이전과 동일한 test_main_b.py를 가질 수 있다.


from fastapi.testclient import TestClient
from .main_b import app

client = TestClient(app)

def test_read_item():
    response = client.get("/items/foo", headers={"X-Token": "coneofsilence"})
    assert response.status_code == 200
    assert response.json() == {
        "id": "foo",
        "title": "Foo",
        "description": "There goes my hero",
    }

def test_read_item_bad_token():
    response = client.get("/items/foo", headers={"X-Token": "hailhydra"})
    assert response.status_code == 400
    assert response.json() == {"detail": "Invalid X-Token header"}

def test_read_inexistent_item():
    response = client.get("/items/baz", headers={"X-Token": "coneofsilence"})
    assert response.status_code == 404
    assert response.json() == {"detail": "Item not found"}

def test_create_item():
    response = client.post(
        "/items/",
        headers={"X-Token": "coneofsilence"},
        json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"},
    )
    assert response.status_code == 200
    assert response.json() == {
        "id": "foobar",
        "title": "Foo Bar",
        "description": "The Foo Barters",
    }

def test_create_item_bad_token():
    response = client.post(
        "/items/",
        headers={"X-Token": "hailhydra"},
        json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"},
    )
    assert response.status_code == 400
    assert response.json() == {"detail": "Invalid X-Token header"}

def test_create_existing_item():
    response = client.post(
        "/items/",
        headers={"X-Token": "coneofsilence"},
        json={
            "id": "foo",
            "title": "The Foo ID Stealers",
            "description": "There goes my stealer",
        },
    )
    assert response.status_code == 400
    assert response.json() == {"detail": "Item already exists"}


5. Run it

  • pytest를 다음과 같이 설치한다.


pip install pytest


  • pytest는 파일과 테스트를 자동으로 감지하여 실행하고, 결과를 다시 보고한다.


  • 다음과 같이 테스트를 실시할 수 있다.


pytest

References