对您的Web服务API进行单元测试应该就是:对您的API进行单元测试。但是,如果使用错误的方法,API测试可能会接管您的整个测试计划。一个好的单元测试策略可以确保两件事:你创建好的测试,你只是测试你的API。
“测试代码”通常组织成一条路径,从单元测试开始,继续进行集成/端到端测试,最后是用户验收测试。在这条道路的开始,单元测试做了两件事——它确认:
- “被测代码”(CUT)在输入输入时会做正确的事情
- CUT将正确的数据传递给处理链中的下一步
之后,您继续进行集成测试,确认“假冒”工作正常(大概只有在单元测试“处理链中的下一件事”以确认它正确完成工作之后)。
使用API,单元测试可能是这个计划的失败之处。因为API是处理链的接口,所以很容易将API的单元测试与整个处理链的集成测试混淆。有效的API测试策略可以防止这种情况发生。
为了演示如何有效地管理API测试,我将通过API单元测试策略(使用[ Telerik Test Studio for APIs)来演示有效的API策略在现实生活中的样子。
API单元测试
与任何其他测试集一样,API测试遵循Arrange-Act-Assert模式。但是,与其他测试不同的是,您无需编写任何代码即可开始测试您的Web服务API—例如,使用Azure的API管理服务,您无需编写任何代码即可定义您的API。因此,您从构成API的URL开始您的API测试,而不是您的代码。
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 API中设置我的项目并使用其基本URL创建我的第一个测试如下所示:
Act
API测试的Act阶段包括使用基本URL、HTTP动词(GET、POST等)以及可能在请求正文和/或标头中的某些值发出HTTP请求。
显而易见的第一个测试是带有基本URL的简单GET请求(我的客户服务中的“获取所有客户”请求)。为了在Test Studio中实现API的第一个测试,我将向我的项目添加一个HTTP请求步骤,并将其动词设置为GET,并将其URL设置为base-url变量。第一个测试看起来像这样:
Assert
对于RESTful API测试,Assert阶段从检查HTTP响应的状态代码开始。此外(如果它对应用程序产生影响),您可以检查响应正文或标头中的值。在Test Studio for API中,我使用HTTP请求的验证选项卡中的设置来检查我的结果。此时(记住没有与此API关联的代码),我只想检查响应中的状态代码是否等于200:
由于尚未创建服务,因此运行此测试可能看起来很愚蠢。但是,如果我运行这个测试并且它成功了,那么我知道我有一个无用的测试:它无法检测到失败。现在运行测试可以快速轻松地检查我的测试质量。此外,在API的Test Studio中,运行测试很容易:只需单击“运行”按钮。
我这样做了,测试不出所料地失败了。Test Studio for APIs在失败的步骤、测试和项目旁边显示传统的红色交通灯图标:
最终,这个测试将变得有用(这就是它的名称“拒绝未经授权的用户”的原因)。不过,现在,我已经证明这个测试可以区分成功和失败。
测试认证
下一步是开始编写API。这将取决于您用于构建服务的开发平台:使用ASP.NET/ASP.NET Core/.NET,下一步将包括创建控制器……但这就是我要做的全部。除了让这些控制器编译所需的代码之外,我不会在控制器的方法中添加任何代码(这可能只是返回200状态代码的返回语句)。使用Azure的API管理,我将定义API,或许还可以使用入站策略返回模拟结果。
此时我不需要任何代码,因为我只想测试是否可以成功访问我的API。更准确地说:这是我发现我是否正确处理身份验证的地方。假设我的API具有身份验证/授权(并且应该),当我重新运行测试时,我现在应该返回401 Unauthorized状态代码。
此时,在API的Test Studio中,我将验证选项卡上的状态代码更新为401,以便拒绝显示为“通过测试”。当我现在重新运行测试时,我已经证明我的服务拒绝未经授权的用户。
这导致了明显的第二个测试:授权用户可以访问服务吗?如果我使用基本身份验证,API的Test Studio将在给定用户名和密码的情况下生成身份验证标头(或者,它将生成客户端ID和密码以支持OAuth)。
这是一个比这两个更简单的示例,它将基本订阅类型标头合并到我将用于第二个测试的请求中:
现在,我将检查指示我的请求实际到达服务的状态代码。您获得的状态代码将根据您用于创建Web服务的工具而有所不同(例如,ASP.NET Web服务将返回200)。如果您使用Azure的API管理并且未设置入站策略,您将获得500状态代码,因为没有实际代码连接到该服务。
我现在可以证明不提供身份验证的客户端无法访问我的服务,而具有适当身份验证的客户端可以。知道这一点让人放心。
而且,现在,我需要开始编写更多代码来实现我的API。这是因为测试我的API的下一步是添加测试以证明无效的授权请求(即API不支持)被拒绝:只读服务应该拒绝更新请求;URL格式错误或有效负载格式错误的请求也应被拒绝。
不过,我的目标很窄:我只想证明我的API针对无效请求返回适当的消息,并为有效请求返回“看起来很合适”的有效负载(Test Studio for APIs让我检查使用JSON路径和XPath返回的有效负载)。我不需要服务返回“真实结果”。
这也意味着我需要有效和无效的有效负载来发送到我的API。基于OpenAPI的工具(如Swagger)可以帮助生成示例有效负载以发送到我的服务。描述预期响应的JSON模式在这里也可以用于验证从我的API返回的格式(在API Test Studi中,编码步骤将让我利用JSON模式来验证我的请求和响应)。
测试接口
我不想做的是测试API的实际功能。API通常出于以下四个目的之一而存在:
- 在某些对象上调用方法
- 将项目写入队列
- 发起事件
- 调用另一个API
这些都不是我的API的责任。当我测试我的API时,我只想确认有效请求被接受并且无效请求被拒绝(使用正确的消息)。最多,我想确保一个有效的请求生成一个格式正确的结果。
最多,如果我的API代码背后的代码调用对象,我将创建模拟对象(在Telerik世界中,我会使用JustMock)来确认我的API:
- 以正确的顺序做出正确的调用
- 将它接收到的任何参数传递给这些对象
- 正确格式化/记录响应(包括异常)
对于写入队列或引发事件的服务,我将确认我的API:
- 引发正确的事件
- 将正确的数据写入正确的队列
- 报告引发事件或正确写入队列的错误
- 与过去引发的事件或队列消息向后兼容
在API Test Studio中,我将使用编码步骤来检查这些条件。
但是,API测试不负责确认API背后的代码(或由API触发的任何进程)是否正常工作。应该有人独立于我的API测试对这些对象或进程进行单元测试。
更准确地说,如果我的API背后的代码引发事件或写入队列,则可能有无限数量的进程从这些队列读取或响应这些事件(我甚至可能不知道所有这些进程)。由于我无法确保我甚至不知道的进程在我的API测试中做的是“正确”的事情,所以我应该尽早放弃这个想法。
集成测试
最后,当然,我们必须证明我的API所属的业务事务可以正确完成——从客户端,通过我的API,一直到任何后端流程。这就是我们进行集成和端到端测试的原因。
这可能涉及作为我的集成测试的一部分返回我的API测试。对于集成测试,我需要创建更复杂的测试,例如,在检查结果之前等待整个过程完成。在API Test Studio中,我可以创建多步骤测试,其中包括等待步骤以在检查预期结果之前暂停。随着我的API测试激增并变得越来越复杂,我需要开始组织这些测试(除了让我创建专用的测试项目,例如API的Test Studio,让我设置文件夹来组织项目内的测试)。
或者我可能不会返回我的API测试:我可能会从调用我的API的客户端开始我的集成/端到端/用户验收测试(在这种情况下,我将使用像Test Studio这样的工具。无论哪种方式,我的重点和意图(以及我的测试内容)与我在对我的API进行单元测试时所做的非常不同。
防止API测试无休止地扩展的唯一方法是识别何时进行单元测试以及何时进行集成/端到端/用户验收测试。要记住的关键是对API进行单元测试意味着只是测试API,而不是在它背后发生的所有事情
https://www.codeproject.com/Articles/5318075/How-To-Unit-Test-Your-API