模拟、存根、伪对象和仿真器:使用编码代理测试 API 调用

当你的代码调用 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。

后续步骤

另请参阅