当你的代码调用 API 时,你需要一种无需等待真实 API 失败即可测试它的方法。 你可以选择 5 种替代方式。 人们对这些名称的使用比较随意,下面说明本文对这些名称的用法:
- 桩:HTTP 客户端或 SDK 调用的进程内替代项,返回预设响应。 它快速且可重复,并测试你的逻辑如何处理你编写的响应。
- 模拟对象:一种还会记录你的代码如何调用它的存根,因此你的测试可以检查这些调用。 它的覆盖范围与存根相同。
- 假服务器:应用通过 HTTP 调用而非真实 API 的小型可工作服务器,通常位于内存中。 它会测试你的 HTTP 客户端,但你必须将应用指向它的 URL。
- 模拟器:服务的本地版本,通常由服务的所有者发布,对于其支持的操作,其行为与真实服务相同。 在依赖它进行错误处理之前,请检查它涵盖哪些限制和故障。
- 拦截代理:位于你的应用和 API 之间的网络上。 你的应用调用真正的 URL,代理会将请求转发出去,或者用你定义的响应来回答其中的一部分请求。 你的应用必须通过代理发送其流量,对于 HTTPS,必须信任代理的证书。
如何测试调用 API 的代码
| Approach | 它测试的内容 | 所需条件 | 就在这时 |
|---|---|---|---|
| 存根或模拟对象 | 针对特定响应的逻辑 | 你的测试框架 | 你在单元测试中测试业务逻辑、解析和错误分支 |
| 模拟服务器 | HTTP 客户端和序列化 | 应用中的基础 URL 设置或配置开关 | API 尚不存在,或者你需要一个稳定的后端来进行 UI 开发 |
| Emulator | 受支持操作的行为接近真实服务 | 其他终结点或连接字符串 | 服务的所有者提供了一个,且你在离线开发 |
| 带测试帐户的真实 API | 真实服务 | 凭据、配额和资金 | 您端到端验证主路径 |
| 拦截代理 | 调用真实 URL 的运行中应用,包括 SDK 重试和响应标头 | 代理设置和证书信任 | 在不更改应用的情况下测试失败、限制和延迟 |
你需要多个这样的项目。 存根使你的单元测试保持快速。 代理显示整个应用在真实 API 出现异常时执行的操作。 有关 2 如何组合在一起的详细信息,请参阅 Dev Proxy 与单元测试。
编码代理能构建什么
当你要求编码智能体让你的应用处理 API 故障并证明其有效时,它会为你选择一种替代方式。 我们想知道哪一个,所以我们运行了一个测试与 3 个编码代理(210 次运行)。 这些任务使用了这样的措辞,如“正确处理速率限制,并证明其有效”、“在不花钱进行实际 API 调用的情况下验证它”和“在没有 OpenAI 密钥或 Internet 连接的情况下运行它”。
- 在要求提供可运行代码的 140 次运行中,有 76% 的运行里智能体手动构建了故障:fetch 存根、
httpx.MockTransport或一次性 HTTP 服务器。 - 在针对调用 GitHub、OpenAI 或天气 API 的应用进行的 105 次运行中,有 61 次运行里智能体向应用添加了基 URL 或配置开关,以便应用能够访问其模拟服务。
- 在真实 API 可用且提示未排除使用它的 75 次运行中,有 0 次使用应用的真实 API URL 测试了该应用。
存根是单元测试的合理选择。 差距在于它们遗漏的内容。智能体的存根返回智能体预期的错误,这可能与 API 返回的错误不匹配。 而且它为访问模拟服务而添加的开关会随你的应用一起发布。
如何使用智能体的测试
- 保留用于测试你的逻辑的存根。 它们速度快,并且测试智能体编写的分支。
- 要求提供真实的错误格式。 要求代理根据提供程序已记录的状态代码、标头和正文字段构造每个模拟错误。 单纯的 429 无法测试你的应用是否读取
retry-after或区分计费错误与速率限制。 - 查看为测试添加的开关。 如果智能体添加基 URL 设置仅仅是为了让测试能够访问模拟服务,请决定是否希望在生产代码中保留该设置。
- 在模拟失败的真实 URL 上运行应用一次。 在交付之前,请检查正在运行的应用、SDK 及其重试策略如何处理 API 自身的错误。 若要逐步查看智能体的错误处理,请参阅如何验证编码智能体编写的错误处理。
在你的应用中试试
Dev Proxy 是用于开发的拦截代理。 它返回为应用已在调用的 URL 定义的响应,且不会对应用的代码进行更改。 在配置中启用 MockResponsePlugin, devproxyrc.json:
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
"plugins": [
{
"name": "MockResponsePlugin",
"enabled": true,
"pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
"configSection": "mocksPlugin"
}
],
"urlsToWatch": [
"https://api.contoso.com/*"
],
"mocksPlugin": {
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/mockresponseplugin.schema.json",
"mocksFile": "mocks.json"
}
}
然后,在 mocks.json 中定义响应。 这个示例中,预测终结点返回带有 Retry-After 标头的 503:
{
"$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/mockresponseplugin.mocksfile.schema.json",
"mocks": [
{
"request": {
"url": "https://api.contoso.com/v1/forecast*",
"method": "GET"
},
"response": {
"statusCode": 503,
"headers": [
{
"name": "Retry-After",
"value": "10"
}
],
"body": {
"error": "Service unavailable"
}
}
}
]
}
像往常一样启动开发代理并运行应用:
devproxy --config-file devproxyrc.json
与模拟不匹配的请求将转到真正的 API。 当需要尚不存在的后端时,CrudApiPlugin 使用内存中数据 模拟 CRUD API 。 若要使一部分请求随机失败,而不是每次都失败,请参阅 “使用随机错误测试我的应用”。 若要安装 Dev Proxy,请参阅 设置 Dev Proxy。