176 lines
6.1 KiB
Python
176 lines
6.1 KiB
Python
|
|
"""基础测试模块。
|
|||
|
|
|
|||
|
|
测试业务逻辑层 (TodoService) 和 API 层 (FastAPI 路由) 的核心功能。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
from httpx import AsyncClient, ASGITransport
|
|||
|
|
|
|||
|
|
from app.main import app
|
|||
|
|
from app.services import TodoService
|
|||
|
|
from app.models import TodoCreate, TodoUpdate
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 单元测试 — TodoService 业务逻辑
|
|||
|
|
# ============================================================
|
|||
|
|
|
|||
|
|
class TestTodoService:
|
|||
|
|
"""TodoService 的单元测试。"""
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def service(self):
|
|||
|
|
"""每个测试使用全新的 TodoService 实例。"""
|
|||
|
|
return TodoService()
|
|||
|
|
|
|||
|
|
def test_create_todo(self, service):
|
|||
|
|
"""测试创建任务:应返回带 ID 和默认字段的有效任务。"""
|
|||
|
|
data = TodoCreate(title="测试任务", description="测试描述")
|
|||
|
|
item = service.create_todo(data)
|
|||
|
|
assert item.id == 1
|
|||
|
|
assert item.title == "测试任务"
|
|||
|
|
assert item.description == "测试描述"
|
|||
|
|
assert item.completed is False
|
|||
|
|
|
|||
|
|
def test_list_todos_empty(self, service):
|
|||
|
|
"""测试空列表:新建服务应返回空列表。"""
|
|||
|
|
items, total = service.list_todos()
|
|||
|
|
assert items == []
|
|||
|
|
assert total == 0
|
|||
|
|
|
|||
|
|
def test_list_todos_with_data(self, service):
|
|||
|
|
"""测试列表:创建多个任务后应能正确返回。"""
|
|||
|
|
service.create_todo(TodoCreate(title="任务1"))
|
|||
|
|
service.create_todo(TodoCreate(title="任务2"))
|
|||
|
|
items, total = service.list_todos()
|
|||
|
|
assert total == 2
|
|||
|
|
assert len(items) == 2
|
|||
|
|
|
|||
|
|
def test_get_todo_by_id(self, service):
|
|||
|
|
"""测试根据 ID 获取任务。"""
|
|||
|
|
created = service.create_todo(TodoCreate(title="查找测试"))
|
|||
|
|
fetched = service.get_todo(created.id)
|
|||
|
|
assert fetched is not None
|
|||
|
|
assert fetched.title == "查找测试"
|
|||
|
|
|
|||
|
|
def test_get_todo_not_found(self, service):
|
|||
|
|
"""测试获取不存在的任务应返回 None。"""
|
|||
|
|
assert service.get_todo(999) is None
|
|||
|
|
|
|||
|
|
def test_update_todo(self, service):
|
|||
|
|
"""测试更新任务:应正确修改标题和完成状态。"""
|
|||
|
|
created = service.create_todo(TodoCreate(title="原标题"))
|
|||
|
|
updated = service.update_todo(
|
|||
|
|
created.id,
|
|||
|
|
TodoUpdate(title="新标题", completed=True),
|
|||
|
|
)
|
|||
|
|
assert updated is not None
|
|||
|
|
assert updated.title == "新标题"
|
|||
|
|
assert updated.completed is True
|
|||
|
|
|
|||
|
|
def test_update_todo_not_found(self, service):
|
|||
|
|
"""测试更新不存在的任务应返回 None。"""
|
|||
|
|
result = service.update_todo(999, TodoUpdate(title="无"))
|
|||
|
|
assert result is None
|
|||
|
|
|
|||
|
|
def test_delete_todo(self, service):
|
|||
|
|
"""测试删除任务:删除后应无法再获取。"""
|
|||
|
|
created = service.create_todo(TodoCreate(title="待删除"))
|
|||
|
|
deleted = service.delete_todo(created.id)
|
|||
|
|
assert deleted is True
|
|||
|
|
assert service.get_todo(created.id) is None
|
|||
|
|
|
|||
|
|
def test_delete_todo_not_found(self, service):
|
|||
|
|
"""测试删除不存在的任务应返回 False。"""
|
|||
|
|
result = service.delete_todo(999)
|
|||
|
|
assert result is False
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ============================================================
|
|||
|
|
# 集成测试 — FastAPI 路由
|
|||
|
|
# ============================================================
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def client():
|
|||
|
|
"""创建测试用的 AsyncClient,绑定到 FastAPI 应用。"""
|
|||
|
|
transport = ASGITransport(app=app)
|
|||
|
|
return AsyncClient(transport=transport, base_url="http://test")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_root(client):
|
|||
|
|
"""测试根路径返回 API 信息。"""
|
|||
|
|
response = await client.get("/")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert "name" in data
|
|||
|
|
assert "version" in data
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_create_todo(client):
|
|||
|
|
"""测试 POST /api/todos 创建任务。"""
|
|||
|
|
response = await client.post("/api/todos", json={
|
|||
|
|
"title": "API 测试任务",
|
|||
|
|
"description": "通过 API 创建",
|
|||
|
|
})
|
|||
|
|
assert response.status_code == 201
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["code"] == 201
|
|||
|
|
assert data["data"]["title"] == "API 测试任务"
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_list_todos(client):
|
|||
|
|
"""测试 GET /api/todos 获取任务列表。"""
|
|||
|
|
# 先创建一条数据
|
|||
|
|
await client.post("/api/todos", json={"title": "列表测试"})
|
|||
|
|
response = await client.get("/api/todos")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
data = response.json()
|
|||
|
|
assert data["code"] == 200
|
|||
|
|
assert data["total"] >= 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_get_todo(client):
|
|||
|
|
"""测试 GET /api/todos/{id} 获取单个任务。"""
|
|||
|
|
create_resp = await client.post("/api/todos", json={"title": "单个查询"})
|
|||
|
|
todo_id = create_resp.json()["data"]["id"]
|
|||
|
|
response = await client.get(f"/api/todos/{todo_id}")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
assert response.json()["data"]["title"] == "单个查询"
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_get_todo_not_found(client):
|
|||
|
|
"""测试 GET /api/todos/{id} 不存在的任务应返回 404。"""
|
|||
|
|
response = await client.get("/api/todos/99999")
|
|||
|
|
assert response.status_code == 404
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_update_todo(client):
|
|||
|
|
"""测试 PUT /api/todos/{id} 更新任务。"""
|
|||
|
|
create_resp = await client.post("/api/todos", json={"title": "待更新"})
|
|||
|
|
todo_id = create_resp.json()["data"]["id"]
|
|||
|
|
response = await client.put(f"/api/todos/{todo_id}", json={
|
|||
|
|
"title": "已更新",
|
|||
|
|
"completed": True,
|
|||
|
|
})
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
assert response.json()["data"]["title"] == "已更新"
|
|||
|
|
assert response.json()["data"]["completed"] is True
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.mark.asyncio
|
|||
|
|
async def test_api_delete_todo(client):
|
|||
|
|
"""测试 DELETE /api/todos/{id} 删除任务。"""
|
|||
|
|
create_resp = await client.post("/api/todos", json={"title": "待删除"})
|
|||
|
|
todo_id = create_resp.json()["data"]["id"]
|
|||
|
|
response = await client.delete(f"/api/todos/{todo_id}")
|
|||
|
|
assert response.status_code == 200
|
|||
|
|
# 再次查询应返回 404
|
|||
|
|
get_resp = await client.get(f"/api/todos/{todo_id}")
|
|||
|
|
assert get_resp.status_code == 404
|