测试概览
Nexent 提供了一个全面的测试框架,确保所有组件的代码质量和可靠性。本指南涵盖了项目中使用的测试策略、工具和最佳实践。
测试理念
我们的测试方法建立在四个核心原则之上:
- 隔离单元:模拟所有外部依赖
- 控制环境:设置精确的测试条件
- 测试接口:专注于输入和输出
- 验证行为:检查结果和交互
这确保了测试的可靠性、快速性,并且不会影响真实的系统或数据。
测试框架
项目使用多种测试框架的组合:
- unittest:Python 标准单元测试框架,用于测试组织和断言
- unittest.mock:用于模拟依赖项和隔离组件
- TestClient from FastAPI:用于测试 API 端点而无需运行实际服务器
- pytest:用于高级测试发现和执行
- coverage:用于代码覆盖率分析和报告
测试结构
test/
├── backend/ # 后端测试
│ ├── app/ # API 端点测试
│ ├── services/ # 服务层测试
│ └── utils/ # 工具函数测试
├── frontend/ # 前端测试(未来)
├── integration/ # 集成测试(未来)
└── run_all_tests.py # 主测试运行器
主要特性
- 🔍 自动发现测试文件 - 自动查找所有
test_*.py
文件 - 📊 覆盖率报告 - 生成控制台、HTML 和 XML 格式的覆盖率报告
- 🔧 自动安装依赖 - 自动安装所需的包(如果需要)
- ✅ 详细输出 - 显示每个测试的运行状态和结果
- 🚫 完全隔离 - 从不接触真实的外部服务
- ⚡ 快速执行 - 无网络延迟或外部服务处理时间
运行测试
快速开始
bash
# 运行所有测试并生成覆盖率报告
cd test
python run_all_tests.py
后端测试
bash
# 仅运行后端测试
python test/backend/run_all_test.py
单个测试文件
bash
# 运行特定测试文件
python -m pytest test/backend/services/test_agent_service.py -v
输出文件
测试完成后,您将找到:
coverage_html/
- 详细的 HTML 格式覆盖率报告coverage.xml
- XML 格式覆盖率报告(用于 CI/CD).coverage
- 覆盖率数据文件- 包含详细测试结果和覆盖率统计的控制台输出
测试策略
1. 依赖隔离
在导入之前模拟外部模块以避免真实连接:
- 模拟数据库连接
- 模拟 ElasticSearch 和其他外部服务
- 测试期间不执行实际的数据库操作
- 模拟 HTTP 客户端以防止网络调用
2. 基于模拟的测试
- 使用 FastAPI 的 TestClient 模拟 HTTP 请求
- 使用模拟对象拦截外部服务调用
- 不发生实际的网络连接或端口绑定
- 模拟身份验证函数以返回可预测的测试值
3. 测试组织
- 测试组织为继承自
unittest.TestCase
的类 - 每个 API 端点或函数都有多个测试用例(成功、失败、异常场景)
- 应用全面的补丁来隔离被测试的代码
- 测试遵循清晰的设置-执行-断言模式
4. API 测试
- 测试 API 端点的正确响应代码、负载结构和错误处理
- 涵盖同步和异步端点
- 通过专门的测试用例测试流式响应
- 彻底测试身份验证和授权
模块补丁技术
测试套件中使用的关键技术是在导入之前修补模块。这可以防止任何真实的外部服务连接。
示例:导入前补丁
python
# 动态确定后端路径
current_dir = os.path.dirname(os.path.abspath(__file__))
backend_dir = os.path.abspath(os.path.join(current_dir, "../../../backend"))
sys.path.append(backend_dir)
# 在导入模块之前设置依赖项补丁
patches = [
patch('botocore.client.BaseClient._make_api_call', return_value={}),
patch('backend.database.client.MinioClient', MagicMock()),
patch('backend.database.client.db_client', MagicMock()),
patch('backend.utils.auth_utils.get_current_user_id',
MagicMock(return_value=('test_user', 'test_tenant'))),
patch('httpx.AsyncClient', MagicMock())
]
# 启动所有补丁
for p in patches:
p.start()
# 现在在应用所有补丁后导入模块
from backend.apps.file_management_app import router
这种方法的优势
- 完全隔离:从不接触真实的外部服务
- 无副作用:测试无法修改生产数据库或服务
- 更快的测试:无网络延迟或外部服务处理时间
- 可预测的结果:测试使用受控的模拟数据以获得一致的结果
- 无端口绑定:FastAPI 应用程序从不绑定到真实的网络端口
测试示例
以下是测试结构化的详细示例:
python
@patch("utils.auth_utils.get_current_user_id")
@patch("database.agent_db.query_all_tools")
async def test_list_tools_api_success(self, mock_query_all_tools, mock_get_current_user_id):
# 设置
mock_get_current_user_id.return_value = ("user123", "tenant456")
expected_tools = [{"id": 1, "name": "Tool1"}, {"id": 2, "name": "Tool2"}]
mock_query_all_tools.return_value = expected_tools
# 执行
result = await list_tools_api(authorization="Bearer fake_token")
# 断言
mock_get_current_user_id.assert_called_once_with("Bearer fake_token")
mock_query_all_tools.assert_called_once_with(tenant_id="tenant456")
self.assertEqual(result, expected_tools)
覆盖率报告
测试套件生成全面的覆盖率报告:
- 控制台输出:逐行覆盖率详细信息
- HTML 报告:
coverage_html/
中的详细覆盖率报告 - XML 报告:用于 CI/CD 集成的覆盖率数据
- 摘要统计:总体覆盖率百分比和缺失行
示例输出
Nexent Community - Unit Test Runner
============================================================
发现的测试文件:
----------------------------------------
• backend/services/test_agent_service.py
• backend/services/test_conversation_management_service.py
• backend/services/test_knowledge_summary_service.py
总计:3 个测试文件
============================================================
运行单元测试和覆盖率
============================================================
test_get_enable_tool_id_by_agent_id ... ok
test_save_message_with_string_content ... ok
test_load_knowledge_prompts ... ok
...
============================================================
覆盖率报告
============================================================
名称 Stmts Miss Cover Missing
--------------------------------------------------------------------------------
backend/services/agent_service.py 120 15 88% 45-50, 78-82
backend/services/conversation_management_service.py 180 25 86% 123-128, 156-162
backend/services/knowledge_summary_service.py 45 8 82% 35-42
--------------------------------------------------------------------------------
总计 345 48 86%
HTML 覆盖率报告生成在:test/coverage_html
XML 覆盖率报告生成:test/coverage.xml
============================================================
✅ 所有测试通过!
依赖项
测试运行器会自动安装所需的包(如果尚未可用):
pytest-cov
- 用于 pytest 覆盖率集成coverage
- 用于代码覆盖率分析pytest
- 用于高级测试发现和执行
最佳实践
- 始终在导入模块之前模拟外部依赖
- 使用描述性的测试名称来解释正在测试的内容
- 遵循设置-执行-断言模式以获得清晰的测试结构
- 测试成功和失败场景以获得全面的覆盖率
- 保持测试独立 - 每个测试都应该能够独立运行
- 使用有意义的模拟数据来代表真实世界的场景
- 用清晰的注释记录复杂的测试场景
这个测试框架确保所有代码更改在部署前都经过彻底验证,在整个 Nexent 平台中保持高代码质量和可靠性。