测试指南
#测试架构
#核心测试栈
#测试层级
#核心命令
#运行测试
hljs bash
# 所有测试(推荐)
python tests/agents/run_tests.py
# 单元测试
python tests/agents/run_tests.py unit
pytest tests/agents/unit -v
# 集成测试 + 覆盖率
python tests/agents/run_tests.py integration
pytest tests/agents --cov=agents --cov-report=html
# 覆盖率报告
python tests/agents/run_tests.py coverage
#测试标记
@pytest.mark.unit- 单元测试(默认)@pytest.mark.integration- 集成测试@pytest.mark.slow- 慢速测试(可跳过)
运行特定标记:
hljs bash
pytest -m "not slow" # 跳过慢速测试
pytest -m integration # 只运行集成测试
#异步测试模式
#基本模式
hljs python
@pytest.mark.asyncio
async def test_agent_processing():
agent = TestAgent()
result = await agent.process({"input": "data"})
assert result["success"]
#Mock 异步函数
hljs python
@pytest.fixture
def mock_async_service():
service = AsyncMock()
service.process.return_value = {"status": "success"}
return service
#测试结构
tests/agents/
├── unit/ # 单元测试(80%)
├── integration/ # 集成测试(15%)
├── e2e/ # 端到端测试(5%)
├── performance/ # 性能测试
└── fixtures/ # 测试数据和Mock
├── factories/ # 数据工厂
└── mocks/ # Mock对象
#常用 Fixtures
hljs python
def test_with_client(test_client):
# FastAPI 测试客户端
response = test_client.post("/papers", json=data)
assert response.status_code == 200
async def test_with_async_client(async_client):
# 异步测试客户端
response = await async_client.post("/papers", json=data)
assert response.status_code == 200
def test_with_sample_data(sample_paper_metadata):
# 使用预定义的测试数据
assert sample_paper_metadata["title"]
#Mock 策略
#常用 Mock
hljs python
# Mock Agent
@pytest.fixture
def mock_pdf_agent():
with patch("agents.claude.pdf_agent.PDFProcessingAgent") as mock:
agent = AsyncMock()
agent.extract_content.return_value = {"text": "content"}
mock.return_value = agent
yield agent
# Mock API 响应
@pytest.fixture
def mock_claude_response():
return {
"content": [{"text": "翻译内容", "type": "text"}],
"usage": {"input_tokens": 100, "output_tokens": 50}
}
#调试技巧
hljs bash
# 详细输出
pytest -v -s tests/agents
# 只运行失败测试
pytest --lf
# 首次失败即停止
pytest -x
# 显示局部变量
pytest --tb=long
#质量要求
- 覆盖率: >80%
- 单元测试: 占比 >70%
- 执行时间: <5 分钟
- 稳定性: >95%
#CI/CD 集成
测试在以下阶段自动运行:
- Ruff 代码检查
- MyPy 类型检查
- 单元测试
- 集成测试
- 覆盖率报告(Codecov)
#最佳实践
- 测试隔离: 每个测试独立,使用 fixtures
- 异步测试: 始终使用
@pytest.mark.asyncio - 命名规范:
test_<function>_<condition>_<expected>() - 覆盖率: 聚焦业务逻辑,测试错误处理路径
#Web UI 测试
#测试技术栈
#测试层级分布
#测试命令
hljs bash
# 进入 UI 目录
cd ui
# 单元测试和集成测试 (Vitest)
npm run test # 运行所有测试
npm run test:watch # 监听模式
npm run test:coverage # 生成覆盖率报告
npm run test:ui # Vitest UI 界面
# E2E 测试 (Playwright)
npm run test:e2e # 运行 E2E 测试
npm run test:e2e:ui # Playwright UI 模式
npm run test:e2e:debug # 调试模式
# 视觉回归测试
npm run test:visual # 更新快照
#目录结构
tests/ui/ # UI 测试统一目录
├── __mocks__/ # Mock 定义
├── fixtures/ # 测试数据
├── helpers/ # 测试辅助函数
├── unit/ # 单元测试
│ ├── components/ # 组件测试
│ └── hooks/ # Hooks 测试
├── integration/ # 集成测试
│ └── pages/ # 页面测试
└── e2e/ # E2E 测试 (Playwright)
ui/ # UI 项目配置
├── vitest.config.ts # Vitest 配置
├── playwright.config.ts # Playwright 配置
└── test/ # 测试配置
└── setup.ts # 测试环境初始化
#单元测试示例
#组件测试
hljs typescript
import { render, screen, fireEvent } from "../../helpers/render";
import { PaperCard } from "@/components/papers/PaperCard";
import { vi } from "vitest";
describe("PaperCard", () => {
const mockPaper = {
// ... paper data
};
it("renders paper information", () => {
render(<PaperCard paper={mockPaper} />);
// ... assertions
});
it("handles click events", () => {
const onClick = vi.fn();
render(<PaperCard paper={mockPaper} onClick={onClick} />);
fireEvent.click(screen.getByRole("article"));
expect(onClick).toHaveBeenCalledWith(mockPaper.id);
});
});
#Hooks 测试
hljs typescript
import { renderHook, act } from "@testing-library/react";
import { usePaperStore } from "@/store/paperStore";
describe("usePaperStore", () => {
it("fetches papers successfully", async () => {
const { result } = renderHook(() => usePaperStore());
await act(async () => {
await result.current.fetchPapers();
});
expect(result.current.loading).toBe(false);
expect(result.current.papers).toHaveLength(3);
});
});
#E2E 测试示例
hljs typescript
import { test, expect } from "@playwright/test";
test.describe("Paper Upload Flow", () => {
test("user can upload and process a paper", async ({ page }) => {
await page.goto("/papers");
// 点击上传按钮
await page.click('button:has-text("上传论文")');
// 选择文件
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles("sample.pdf");
// 等待上传完成
await expect(page.locator("text=上传成功")).toBeVisible();
// 验证论文出现在列表中
await expect(page.locator("text=sample.pdf")).toBeVisible();
});
});
#Mock 策略
#MSW API Mock
hljs typescript
#### MSW API Mock
```typescript
// tests/__mocks__/handlers.ts
import { http, HttpResponse } from "msw";
import { papers } from "../fixtures/papers";
export const handlers = [
http.get("/api/papers", ({ request }) => {
// ... implementation
return HttpResponse.json({
success: true,
data: papers,
});
}),
];
#测试数据管理
#使用 Faker 生成测试数据
hljs typescript
// tests/helpers/factory.ts
import { faker } from "@faker-js/faker";
import { Paper } from "@/types";
export const createPaper = (overrides: Partial<Paper> = {}): Paper => ({
id: faker.datatype.uuid(),
title: faker.lorem.words(5),
authors: [faker.name.fullName(), faker.name.fullName()],
abstract: faker.lorem.paragraphs(3),
status: faker.helpers.arrayElement(["uploaded", "processing", "processed"]),
created_at: faker.date.past().toISOString(),
...overrides,
});
#CI/CD 集成
UI 测试已集成到 GitHub Actions 中:
-
触发条件:
ui/目录下的文件变更 -
测试流程:
- 安装依赖
- 运行单元测试(带覆盖率)
- 构建应用
- 运行 E2E 测试
- 上传测试报告
-
报告生成:
- 覆盖率报告上传到 Codecov
- E2E 测试结果保存为 Artifacts
- 失败时自动上传截图
#质量标准
-
覆盖率要求:
- 全局:>80%
- 核心组件:>90%
- 状态管理:>95%
-
性能指标:
- 单元测试执行时间 < 2 分钟
- E2E 测试执行时间 < 5 分钟
- 测试稳定性 > 95%
-
测试策略:
- 每个组件必须有基础渲染测试
- 关键用户流程必须有 E2E 测试
- API 集成必须有 Mock 测试