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