<aside>

</aside>

챕터 소개 🚀

Hello World API는 이제 안녕! 👋 오늘은 진짜 애플리케이션처럼 보이도록 우리의 Todo API에 살을 붙여보는 시간입니다. 사용자가 할 일(Todo)을 생성하고, 조회하는 기본적인 CRUD 중 생성과 조회 기능을 추가할 거예요. 그리고 기능이 추가되면 무엇을 해야 할까요? 바로 테스트! TDD(테스트 주도 개발) 사이클을 가볍게 맛보며, API가 의도대로 잘 동작하는지 검증하는 테스트 코드도 함께 확장해 보겠습니다.


챕터 목표 🎯


이번 챕터에서 사용되는 전체 코드 및 프로젝트 구조 📂

src/todo_api/main.pytests/test_main.py 파일이 크게 업데이트됩니다!

todo_api/
├── .venv/
├── poetry.lock
├── pyproject.toml
├── README.md
├── src/
│   └── todo_api/
│       ├── __init__.py
│       └── main.py      <-- 기능 추가!
└── tests/
    ├── __init__.py
    └── test_main.py     <-- 테스트 확장!

src/todo_api/main.py

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List

# FastAPI 애플리케이션 인스턴스를 생성합니다.
app = FastAPI()

# Pydantic 모델 정의: API의 데이터 형식을 지정합니다.
class TodoItem(BaseModel):
    id: int
    title: str = Field(min_length=1, max_length=100)
    completed: bool = False

class CreateTodoItemRequest(BaseModel):
    title: str = Field(min_length=1, max_length=100)

# 실제 데이터베이스 대신 사용할 인메모리 저장소입니다.
# 간단한 실습을 위해 리스트와 딕셔너리를 사용합니다.
db: List[TodoItem] = []
id_counter = 0

# 루트 경로
@app.get("/")
def read_root():
    return {"message": "Welcome to the Todo API!"}

# 새로운 Todo 아이템 생성
@app.post("/todos/", response_model=TodoItem, status_code=201)
def create_todo_item(request: CreateTodoItemRequest):
    global id_counter
    id_counter += 1
    new_item = TodoItem(id=id_counter, title=request.title, completed=False)
    db.append(new_item)
    return new_item

# 모든 Todo 아이템 조회
@app.get("/todos/", response_model=List[TodoItem])
def get_all_todo_items():
    return db

tests/test_main.py

import pytest
from httpx import AsyncClient, ASGITransport
from todo_api.main import app

# 비동기 테스트를 위한 pytest 마커
@pytest.mark.asyncio
async def test_read_root():
    async with AsyncClient(transport=ASGITransport(app=app), base_url="<http://test>") as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Welcome to the Todo API!"}

@pytest.mark.asyncio
async def test_create_and_get_todo():
    # 'async with'를 사용하여 테스트용 클라이언트를 생성합니다.
    async with AsyncClient(transport=ASGITransport(app=app), base_url="<http://test>") as ac:
        # 1. 새로운 Todo 아이템 생성
        new_todo_title = "Learn FastAPI"
        response = await ac.post("/todos/", json={"title": new_todo_title})

        # 생성 요청이 성공했는지 확인 (상태 코드 201)
        assert response.status_code == 201

        # 응답 본문이 올바른지 확인
        created_item = response.json()
        assert created_item["title"] == new_todo_title
        assert created_item["completed"] is False
        assert "id" in created_item

        # 생성된 아이템의 ID를 저장
        item_id = created_item["id"]

        # 2. 모든 Todo 아이템 목록 조회
        response = await ac.get("/todos/")

        # 조회 요청이 성공했는지 확인 (상태 코드 200)
        assert response.status_code == 200

        # 응답이 리스트 형태인지, 방금 만든 아이템이 포함되어 있는지 확인
        items_list = response.json()
        assert isinstance(items_list, list)
        assert len(items_list) > 0

        # 목록에서 방금 만든 아이템을 찾아서 검증
        found = any(item['id'] == item_id and item['title'] == new_todo_title for item in items_list)
        assert found, "Created item not found in the list!"


강의 내용 📖

API의 입출력 명세서, Pydantic 모델 📜