Skip to content

FastAPI 실습 코드

라이브러리 설치

fastapi
uvicorn  # fastapi는 개발서버가 없기에 별도 서버 필요

Hello World : JSON 응답

entry.py

테스트 수행

자동화된 테스트를 수행합니다.

pytest를 활용한 테스트

entry.py 파일과 같은 경로에 test_entry.py 파일을 둡니다.

test_entry.py
import pytest
from fastapi.testclient import TestClient
from entry import app

# TestClient 인스턴스 생성
client = TestClient(app)


def test_index():
    """메인 페이지 테스트"""
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"Hello": "World!"}


def test_article_detail():
    """아티클 상세 페이지 테스트"""
    response = client.get("/articles/100/")
    assert response.status_code == 200

    data = response.json()
    assert data["id"] == 100


def test_post_detail():
    """포스트 상세 페이지 테스트 - 정상적인 ID"""
    # 1-100 범위의 유효한 post_id로 테스트
    test_ids = [1, 42, 50, 99, 100]

    for post_id in test_ids:
        response = client.get(f"/posts/{post_id}/")
        assert response.status_code == 200

        data = response.json()
        assert data["id"] == post_id
        assert data["title"] == f"포스팅 #{post_id}"
        assert data["content"] == "..."


def test_post_detail_invalid_id():
    """포스트 상세 페이지 테스트 - 잘못된 ID 타입"""
    # 문자열을 전달하면 422 Unprocessable Entity 에러 발생
    response = client.get("/posts/abc/")
    assert response.status_code == 422

    # 에러 메시지 확인
    error_data = response.json()
    assert "detail" in error_data
    assert len(error_data["detail"]) > 0
    assert error_data["detail"][0]["type"] == "int_parsing"


def test_post_detail_float_id():
    """포스트 상세 페이지 테스트 - 실수형 ID"""
    # 실수를 전달해도 422 에러
    response = client.get("/posts/3.14/")
    assert response.status_code == 422


def test_nonexistent_endpoint():
    """존재하지 않는 엔드포인트 테스트"""
    response = client.get("/nonexistent/")
    assert response.status_code == 404


def test_post_detail_edge_cases():
    """포스트 상세 페이지 테스트 - 경계값"""
    # 유효한 경계값 (1, 100)
    response = client.get("/posts/1/")
    assert response.status_code == 200
    assert response.json()["id"] == 1

    response = client.get("/posts/100/")
    assert response.status_code == 200
    assert response.json()["id"] == 100

    # 무효한 경계값 테스트
    # 0 (1 미만)
    response = client.get("/posts/0/")
    assert response.status_code == 404
    assert "1 이상 100 이하" in response.json()["detail"]

    # 101 (100 초과)
    response = client.get("/posts/101/")
    assert response.status_code == 404
    assert "1 이상 100 이하" in response.json()["detail"]

    # 음수
    response = client.get("/posts/-1/")
    assert response.status_code == 404
    assert "1 이상 100 이하" in response.json()["detail"]

    # 매우 큰 수
    large_id = 9999999999
    response = client.get(f"/posts/{large_id}/")
    assert response.status_code == 404
    assert "1 이상 100 이하" in response.json()["detail"]


class TestResponseStructure:
    """응답 구조 테스트 클래스"""

    def test_index_response_type(self):
        """인덱스 응답이 JSON 형식인지 확인"""
        response = client.get("/")
        assert response.headers["content-type"] == "application/json"

    def test_article_response_structure(self):
        """아티클 응답 구조 확인"""
        response = client.get("/articles/100/")
        data = response.json()

        # 필수 필드 확인
        required_fields = ["id", "title", "content"]
        for field in required_fields:
            assert field in data

        # 데이터 타입 확인
        assert isinstance(data["id"], int)
        assert isinstance(data["title"], str)
        assert isinstance(data["content"], str)

    def test_post_response_structure(self):
        """포스트 응답 구조 확인"""
        response = client.get("/posts/42/")
        data = response.json()

        # 필수 필드 확인
        required_fields = ["id", "title", "content"]
        for field in required_fields:
            assert field in data

        # 데이터 타입 확인
        assert isinstance(data["id"], int)
        assert isinstance(data["title"], str)
        assert isinstance(data["content"], str)


@pytest.mark.parametrize("post_id,expected_title", [
    (1, "포스팅 #1"),
    (50, "포스팅 #50"),
    (100, "포스팅 #100"),
])
def test_post_detail_parametrized(post_id, expected_title):
    """파라미터화된 포스트 상세 테스트 - 유효한 범위"""
    response = client.get(f"/posts/{post_id}/")
    assert response.status_code == 200
    assert response.json()["title"] == expected_title


@pytest.mark.parametrize("post_id", [0, -1, 101, 200, 999, -100])
def test_post_detail_invalid_range(post_id):
    """파라미터화된 포스트 상세 테스트 - 무효한 범위"""
    response = client.get(f"/posts/{post_id}/")
    assert response.status_code == 404
    assert "1 이상 100 이하" in response.json()["detail"]


# 실행 방법:
# pytest test_entry.py
# pytest test_entry.py -v  # 상세 출력
# pytest test_entry.py::test_index  # 특정 테스트만 실행
# pytest test_entry.py -k "post"  # 이름에 "post"가 포함된 테스트만 실행

실행 방법

pip install pytest

pytest -v test_entry.py

생성 요청 + 입력값 유효성 검사

from pydantic import BaseModel

@app.post("/posts/new/")
def post_new(post: PostCreate):  # TODO: json 만 지원? form data 는?
    # TODO: 호출 전에 Item을 통한 유효성 검사 끝.
    return {"item": item}

Comments