2. 测试金字塔与API测试
2.1 测试金字塔模型
测试金字塔(Test Pyramid)由 Mike Cohn 在 2009 年提出,是指导自动化测试分层投入的核心模型。它将测试按粒度、执行速度和维护成本分为三层:底层单元测试、中层集成测试、顶层端到端(E2E)测试。
核心原则: 底层测试数量多、执行快、维护成本低;顶层测试数量少、执行慢、维护成本高。团队应遵循“底层多写、顶层少写”的投入比例。
推荐比例参考: 单元测试约占 70%,集成测试约占 20%,E2E 测试约占 10%。该比例可根据项目复杂度适当调整,但不应颠倒。
2.2 各层测试对比
| 维度 | 单元测试 | 集成测试 | E2E 测试 |
|---|---|---|---|
| 测试范围 | 单个函数/方法/类 | 模块间交互/接口 | 完整用户流程 |
| 执行速度 | 毫秒级 | 秒级 | 分钟级 |
| 依赖隔离 | 完全隔离(Mock) | 部分真实依赖 | 全部真实依赖 |
| 故障定位 | 精准到代码行 | 定位到模块边界 | 定位到流程步骤 |
| 维护成本 | 低 | 中 | 高 |
| 运行频率 | 每次提交 | 每日构建 | 预发布/回归 |
2.3 API测试在金字塔中的位置
API 测试横跨集成测试和部分单元测试两层,是金字塔中承上启下的关键环节。它既验证服务端接口的请求/响应契约,又通过 Mock 技术隔离外部依赖,实现快速、稳定的测试反馈。
- 在集成测试层: 验证 API 端点与数据库、缓存、消息队列等真实组件的交互是否正确。
- 在单元测试层: 对 API 处理函数(Handler/Controller)进行 Mock 测试,隔离 HTTP 请求和下游服务。
- 不推荐在 E2E 层: 全链路 E2E 测试应覆盖关键业务流程,而非每个 API 端点。
常见误区: 团队常将大量 API 测试放在 E2E 层执行,导致测试执行时间过长、环境不稳定、故障定位困难。正确的做法是将 API 测试下沉到集成测试层,配合 Mock 实现快速反馈。
2.4 Mock在API测试中的角色
Mock 是 API 测试中实现依赖隔离和故障模拟的核心技术。在测试金字塔中,Mock 主要服务于集成测试和单元测试两层,帮助测试人员摆脱外部依赖的束缚。
Mock 的核心作用
| 作用 | 说明 | 典型场景 |
|---|---|---|
| 依赖隔离 | 用模拟对象替代真实的外部服务、数据库或消息队列 | 第三方支付 API、短信网关 |
| 状态控制 | 精确控制被模拟服务的返回状态码、响应体和延迟 | 模拟 500 错误、超时、限流 |
| 行为验证 | 验证被测代码是否正确调用了 Mock 对象的方法和参数 | 确认请求头、请求体、调用次数 |
| 加速执行 | 消除网络 I/O 和外部处理延迟,将测试执行时间从秒级降至毫秒级 | CI/CD 流水线中的快速反馈 |
| 故障注入 | 模拟真实环境中难以触发的异常和边界条件 | 数据库连接断开、磁盘满、认证失败 |
Mock 在 API 测试中的典型工作流
- 识别外部依赖: 列出 API 处理函数中调用的所有外部服务、数据库、缓存等。
- 创建 Mock 对象: 使用 Mock 框架(如 unittest.mock、pytest-mock)创建模拟对象。
- 配置预期行为: 设置 Mock 对象的返回值、异常或副作用。
- 执行被测 API: 调用 API 处理函数,触发对 Mock 对象的调用。
- 验证交互结果: 断言 Mock 对象是否被正确调用,以及 API 返回值是否符合预期。
Mock 使用原则: 只 Mock 自己不拥有的依赖(第三方服务、基础设施),不要 Mock 自己团队维护的内部模块。过度 Mock 会导致测试与实现耦合过紧,降低测试的有效性。
Mock 与 Stub 的区别
| 维度 | Mock | Stub |
|---|---|---|
| 核心目的 | 验证行为(是否被调用、调用参数) | 提供预设返回值 |
| 状态验证 | 支持(验证交互行为) | 不支持(仅返回数据) |
| 适用场景 | 需要验证调用顺序、次数、参数 | 只需要模拟返回值,不关心调用细节 |
| 复杂度 | 较高 | 较低 |
实践建议: 在 API 测试中优先使用 Stub 提供返回值,仅在需要验证调用行为(如日志记录、重试机制、回调通知)时才使用 Mock。保持测试简单,避免过度规格化。
# Python 示例:使用 pytest-mock 对 API 处理函数进行 Mock 测试
import pytest
from unittest.mock import Mock
def test_get_user_api(mocker):
# 1. 创建 Mock 对象替代外部数据库查询
mock_db = Mock()
mock_db.get_user.return_value = {"id": 1, "name": "Alice"}
# 2. 注入 Mock 到 API 处理函数
mocker.patch("app.api.get_user_db", return_value=mock_db)
# 3. 执行被测 API
from app.api import get_user_handler
result = get_user_handler(user_id=1)
# 4. 验证结果
assert result["name"] == "Alice"
mock_db.get_user.assert_called_once_with(1)