65.9K
CodeProject 正在变化。 阅读更多。
Home

如何对 API 进行单元测试

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.07/5 (7投票s)

2021年11月23日

CPOL

10分钟阅读

viewsIcon

19381

对您的 Web Service API 进行单元测试应该就是这样:对您的 API 进行单元测试。但如果方法不对,API 测试可能会占据您整个的测试计划。出色的单元测试策略确保两件事:您创建了好的测试,并且您只测试您的 API。

对您的 Web Service API 进行单元测试应该就是这样:对您的 API 进行单元测试。但如果方法不对,API 测试可能会占据您整个的测试计划。出色的单元测试策略确保两件事:您创建了好的测试,并且您只测试您的 API。

“测试代码”通常会组织成一个 路径,该路径以单元测试开始,继续到集成/端到端测试,最后是用户验收测试。在该路径的开头,单元测试会做两件事——它会确认

  • 当“被测代码”(CUT)接收输入时,它会执行正确的操作
  • CUT 会将正确的数据传递给处理链中的下一个环节

在此之后,您将进入集成测试,在集成测试中,您需要确认“传递”工作正常(可能只有在单元测试了“处理链中的下一个环节”以确认其正确执行后)。

对于 API 而言,单元测试可能会导致此计划出现问题。由于 API 是处理链的接口,因此很容易将 API 的单元测试与整个处理链的集成测试混淆。有效的 API 测试策略可以防止这种情况发生。

为了演示如何有效地管理您的 API 测试,我将通过一个 API 单元测试策略(使用 [Telerik Test Studio for APIs)来演示一个有效的 API 策略在现实生活中的样子。

API 单元测试

与任何其他测试集一样,API 测试遵循 Arrange-Act-Assert(安排-执行-断言)模式。然而,与其他测试不同的是,您甚至可以在不编写任何代码的情况下开始测试您的 Web Service API——例如,使用 Azure 的 API Management 服务,您可以完全不编写代码即可定义您的 API。因此,您开始 API 测试是以构成您 API 的 URL 为起点,而不是以您的代码为起点。

Arrange

构成 RESTful 服务 API 的 URL 通常会共享一个基础,所有对该服务的请求都会在基础之上进行变体。例如,以下两个 URL 可以支持对客户服务的所有基本 CRUD 操作

  • http://customers:检索所有客户;添加一个客户,系统提供客户 ID
  • http://customers/A123:检索、更新或删除一个客户;添加一个客户,指定客户 ID(在本例中为客户“A123”)

对于这个案例研究,我将使用 http://customers 作为我的基础 URL。Test Studio for APIs 通过创建一个项目级变量(通常称为 base-url——但您也可以将变量命名为任何您想要的名称)来支持此设计。这意味着我需要用于基本 CRUD 操作的两个 URL 看起来像这样(Test Studio for APIs 使用双法式花括号标记测试中的变量)

  • {{base-uri}}:检索所有客户;添加一个客户,系统提供客户 ID
  • {{base-uri}}/A123:检索、更新或删除一个客户;添加一个客户,指定客户 ID

在 Test Studio for APIs 中,设置我的项目并创建带有基础 URL 的第一个测试看起来是这样的

Act

API 测试的 Act(执行)阶段包括使用基础 URL、HTTP 动词(GET、POST 等)以及可能在请求正文和/或头中的一些值来发出 HTTP 请求。

显而易见的第一个测试是使用基础 URL 的简单 GET 请求(在我客户服务中的“获取所有客户”请求)。要在 Test Studio for APIs 中实现这个第一个测试,我会向我的项目添加一个 HTTP 请求步骤,将其动词设置为 GET,URL 设置为 just the base-url 变量。第一个测试看起来是这样的

断言(Assert)

对于 RESTful API 测试,Assert(断言)阶段从检查 HTTP 响应的状态码开始。此外(如果这对应用程序有影响),您可以检查响应正文或头中的值。在 Test Studio for APIs 中,我使用 HTTP 请求的 verification(验证)选项卡中的设置来检查我的结果。此时(请记住,此 API 没有关联任何代码),我只想检查响应中的状态码是否等于 200

由于服务甚至尚未创建,运行此测试可能看起来很愚蠢。但是,如果我运行此测试并且它成功了,那么我就知道我有一个无用的测试:它无法检测失败。现在运行测试是对我的测试质量进行快速简便的检查。此外,在 Test Studio for APIs 中,运行测试很容易:只需单击“运行”按钮。

我这样做之后,不出所料,测试失败了。Test Studio for APIs 在失败的步骤、测试和项目旁边显示传统的红色交通灯图标

最终,这个测试将变得有用(这就是它被命名为“拒绝未经授权的用户”的原因)。但目前,我已经证明了这个测试可以区分成功和失败。

测试身份验证

下一步是开始编写 API。这取决于您用来构建服务的开发平台:使用 ASP.NET/ASP.NET Core/.NET,下一步将是创建控制器……但我只会这样做。我不会在控制器的实际方法中添加任何代码,除了让这些控制器编译所需的部分(可能只是一个返回 200 状态码的 return 语句)。使用 Azure API Management,我将定义 API,并可能使用入站策略返回模拟结果。

此时我不需要任何代码,因为我只想测试我是否能够成功访问我的 API。更准确地说:这是我找出身份验证是否处理正确的地方。假设我的 API 已经设置了身份验证/授权(它应该这样做),当我重新运行测试时,我现在应该收到一个 401 Unauthorized 状态码。

此时,在 Test Studio for APIs 中,我将验证选项卡上的状态码更新为 401,以便拒绝显示为“测试通过”。现在重新运行我的测试,我已经证明了我的服务拒绝未经授权的用户。

这引出了显而易见的第二个测试:授权用户是否可以访问服务?如果我使用 Basic 身份验证,Test Studio for APIs 将提供用户名和密码,生成身份验证头(或者,它将生成客户端 ID 和密钥来支持 OAuth)。

这里有一个比前两个更简单的例子,它将一个基本的订阅类型头包含在我将用于第二个测试的请求中

现在我将检查一个状态码,该状态码表示我的请求确实已到达服务。您获得的状态码将取决于您用来创建 Web Service 的工具(例如,ASP.NET Web Service 将返回 200)。如果您使用的是 Azure API Management 并且没有设置入站策略,您将获得一个 500 状态码,因为没有实际的代码连接到服务。

我现在可以证明不提供身份验证的客户端无法访问我的服务,而具有适当身份验证的客户端可以。知道这一点令人欣慰。

现在,我需要开始编写更多代码来实际实现我的 API。这是因为测试我的 API 的下一步是添加测试以证明未经授权但有效的请求(即 API 不支持的请求)被拒绝:只读服务**应该**拒绝更新请求;具有格式错误的 URL 或格式不正确的有效负载的请求也应该被拒绝。

不过,我这里的目标很明确:我只想证明我的 API 返回了无效请求的适当消息,以及有效请求的“看起来适当”的有效负载(Test Studio for APIs 允许我使用 JSON 路径和 XPath 来检查返回的有效负载)。我不需要服务返回“真实结果”。

这也意味着我需要向我的 API 发送有效和无效的有效负载。像 Swagger 这样的 OpenAPI 工具在这里可以帮助生成要发送到我的服务的示例有效负载。描述预期响应的 JSON Schema 在验证从我的 API 返回的内容格式方面也很有用(在 Test Studio for APIs 中,代码步骤将允许我利用 JSON Schema 来验证我的请求和响应)。

测试接口

我**不**想做的是测试 API 的实际功能。API 通常有四种用途之一

  • 调用某些对象上的方法
  • 将项目写入队列
  • 引发事件
  • 调用另一个 API

这些都不是我 API 的责任。当我测试我的 API 时,我只想确认有效请求被接受,无效请求被拒绝(并带有正确的消息)。最多,我想确保一个有效请求会生成一个格式正确的响应。

最多,如果我的 API 代码后面的代码调用对象,我将创建模拟对象(在 Telerik 世界中,我将使用 JustMock)来确认我的 API

  • 以正确的顺序执行正确的调用
  • 将接收到的任何参数传递给这些对象
  • 正确格式化/记录响应(包括异常)

对于写入队列或引发事件的服务,我将确认我的 API

  • 引发正确的事件
  • 将正确的数据写入正确的队列
  • 正确报告引发事件或写入队列时的错误
  • 与过去引发的事件或写入的消息向后兼容

在 Test Studio for APIs 中,我将使用代码步骤来检查这些条件。

然而,确认 API 背后代码(或 API 触发的任何过程)是否正常工作不是 API 测试的责任。应该有人独立于我的 API 测试来单元测试这些对象或过程。

更准确地说,如果我的 API 后面的代码引发了一个事件或写入了一个队列,那么可能有无数个进程从这些队列读取或对这些事件做出响应(我甚至可能不知道所有这些进程)。由于我无法在我的 API 测试中确保我甚至不知道的进程在做“正确”的事情,因此我应该及早放弃这个想法。

集成测试

当然,最终,我们必须证明我的 API 所属的业务事务能够被正确完成——从客户端,通过我的 API,一直到任何后端进程。这就是我们进行集成和端到端测试的原因。

这可能涉及在集成测试中返回到我的 API 测试。对于集成测试,我需要创建更复杂的测试,例如,在检查结果之前等待整个进程完成。在 Test Studio for APIs 中,我可以创建多步测试,其中包括一个 Wait 步骤,在检查预期结果之前暂停。随着我的 API 测试的激增和日益复杂,我需要开始组织这些测试(除了允许我创建专用测试项目之外,Test Studio for APIs 还允许我设置文件夹来组织项目内的测试)。

或者我可能不会返回到我的 API 测试:我可能会从调用我的 API 的客户端开始我的集成/端到端/用户验收测试(在这种情况下,我将使用 Test Studio 这样的工具)。无论哪种方式,我的焦点和意图(以及我的测试内容)与我进行 API 单元测试时非常不同。

防止您的 API 测试无限扩展的唯一方法是识别您何时在进行单元测试,何时在进行集成/端到端/用户验收测试。要记住的关键是,对 API 进行单元测试意味着只测试 API 本身——而不是它后面发生的所有事情

© . All rights reserved.