测试 REST API
测试 REST API
如何 不是 使用对象映射器。
Copyright: Fotostudiokok, www.fotostudiokok.nl
对象映射器与 JSON 字符串文字
Web 服务通常围绕 REST API 构建。并且有不同的方法可以用来测试它们。在这篇文章中,我们将看看并列 对象映射器和 JSON 字符串文字 .特别是在过去 10 年中,我看到它们被用于众多项目的方式。有一种使用方法 对象映射器 以比本文场景中描述的更好的方式,但由于我没有遇到它们,我将分别解决它们。
让我们首先说:我认为在实现细节上测试软件行为是一种很好的做法。如果应用正确,这意味着我们能够重构代码而无需更改测试。这符合重构的定义:
[重构](应用程序或软件的源代码)以便在不改变功能的情况下改进操作。
让我们把它付诸实践。第一个策略是使用 对象映射器 将 json 解析为对象,反之亦然。第二个测试将使用 字符串文字 与 JSON 请求和响应。
首先,我们必须设置测试装置。
在这个例子中,我们将使用 春天 和 @WebMvcTest 为了防止一个完整的 spring 上下文启动,我们需要的是 Spring Web MVC 组件 .此外,我们将使用 模拟Mvc 对我们的 REST 端点进行 HTTP 调用。和 Kotlin 模块 是必要的 对象映射器 能够使用 Kotlin 的 数据类 .
这 对象映射器 测试:
和 s 字符串字面量 测试:
请注意,我选择不检查完整的 JSON 响应,而只检查对该特定测试重要的字段。稍后再详细介绍。
两个测试都调用这个端点:
好的,现在我们有一个带有 REST 端点的小应用程序。这是由提到的两种策略测试的。现在,我们将研究多个场景,我们将在这些场景中扩展此应用程序,以比较哪种测试策略给我们带来最佳结果。
场景:添加非必填字段
向请求和响应添加非必需字段的非破坏性更改。场 年龄 已添加到请求中。
结论: 全部 对象映射器 调用此端点的测试将产生编译错误。它在期待 年龄 在构造函数或请求中设置。
如果这个对象的创建是用 工厂模式 ,那么它可能会导致“仅”一次更改,否则可能意味着整个测试代码中有许多更改;所有调用构造函数的地方。
然而,所有的 字符串字面量 测试正在编译并且仍然绿色运行,无需任何更改!我们只需要 添加 涵盖此新功能的测试。换句话说,我们的测试对扩展开放,对修改关闭; 开闭原则。
请注意,如果您确实希望在添加不需要的字段时测试失败,则可以不将检查限制在测试中的特定字段,而是检查完整的 JSON 响应是否等于预期。
向响应对象添加非中断、非必需字段时会发生完全相同的情况。在这种情况下,我的意思是 端点 不介意 年龄 被客户端忽略 要求 和 回复, 它将保持默认值: 0。 因此,这 端点 可以扩展,客户可以继续使用以前的版本(没有 年龄) 直到他们准备好更新。
结论: 所有的 对象映射器 测试需要再次更改,而 s 字符串字面量 测试不需要任何修改。
场景:更改现有字段的名称
更改现有字段的名称是一项重大更改。小心这些更改很重要,因为一旦部署软件,它可能会意外地导致与客户端的互操作性问题。这可能会发生 不是 直到它被部署到生产中,这取决于所应用的测试策略。我们越早发现错误,它对客户的影响就越小,修复的速度也就越快。所以让我们看一下代码中的一个小改动。 注意:我并没有将这种更改定义为最严格意义上的重构,因为我们的 API 更改会导致外部客户端的功能更改。 这是一个可能是有意或无意的更改(即通过不监督其他代码的影响取决于它),但让我们看看我们的测试的后果。
结论: 有趣的是所有 对象映射器 在这个重大更改之后,测试是绿色的,甚至没有编译错误。他们完全找不到我们所做的这一重大改变。然而,s 字符串字面量 测试失败时,它们会识别出发生了重大变化并警告程序员。好东西我们添加了 字符串字面量 测试,或者我们可能只有在部署到生产环境时才注意到它。
场景:使用日期
对于这种情况,我添加了一个 创建 端点响应中的字段:
以下是更新的测试用例:
现在让我们再次更改日期的格式:这可能是偶然的(即仅在域或数据库等其他层中进行更改)或故意的:
结论: 奇怪,所有o 对象映射器 尽管我们再次对 API 进行了重大更改,但测试仍然是绿色的!更可靠的s 字符串字面量 然而,测试已经注意到了重大变化并且测试未能警告程序员,非常好。
场景:更改对象映射器设置
可以将自定义序列化程序添加到对象映射器中。它可以帮助将某个类解析为 json 或如何 枚举 值应该被表示。
这里发生的是返回给调用者的 JSON 响应包含两个字段: 上 和 降低 .
但是测试将响应反序列化回对象 类型 ;这意味着它不再了解这些领域。这里是 对象映射器 测试:
因此,此时甚至无法测试客户端接收或发送的确切消息。我们拥有的只是我们的 枚举类类型。
和 字符串字面量 测试:
这个 字符串字面量 现在可以准确验证从客户端发送和接收的消息。
此外,如果我们现在对序列化器和反序列化器做一个小改动,例如:
然后我们看到与其他示例相同的问题。
结论: o 对象映射器 进行这些重大更改后,测试保持绿色,而 s 字符串字面量 测试失败,从而向开发人员表明合同已经以破坏性的方式发生变化。
场景:(意外)域值泄漏
在视图层不仅依赖于 逻辑 例如来自域层,但也来自 价值观, 那么这可能会导致 API 意外损坏。例如:
这里我们有一个 枚举:类型 在应用程序的领域层(考虑干净/六边形/洋葱分层/端口和适配器架构)。这个 枚举 由应用程序的不同模块使用,例如 看法 ** __** 层包含一个端点和一个 存储库 连接到 MYSQL 数据库。
在此响应中发生了错误,因为封装被破坏了。我们现在公开一个域值 AMIN 或 SUPER_USER 给外部客户。这意味着我们不能再改变我们的域枚举值而不破坏我们的客户。让我们稍微改变一下以向您展示我的意思:
结论: 如果我们再次运行测试,我们会看到我们所有的 对象映射器 测试成功。同样,它没有注意到我们的 API 发生了重大变化!幸运的是 字符串字面量 测试确实注意到它,因为它期望 超级用户
被退回而不是 POWER_USER
,我们的客户也是如此。
这些变化可能非常微妙,它只是对域级别值的简单重构,但如果测试没有正确完成,它可能会被忽视!
使用对象映射器测试行为
就像本文开头提到的那样,我现在将通过一个示例 对象映射器 ,与 字符串字面量 测试。我们先问一个问题:为什么会这样 对象映射器 未能在我们的 API 中找到重大更改?这不是因为 对象映射器 就其本身而言,这是因为我们在告诉 对象映射器 在我们的测试中使用: PostUserRequest 和 PostUserResponse 对象。哪些是实现细节,而不是行为!如果我们可以在不更改测试的情况下更改整个实现,那就太好了,所以我们不应该使用那些 要求 和 回复 对象。这 字符串字面量 test 没有使用这些实现细节,因此在发生重大更改时它会失败测试。让我们创建一个测试,使用 对象映射器 不使用实现细节:
我添加了两个新的 数据类 : TestPostUserRequest 和 测试后用户响应。 让我们再次尝试场景 2,通过更改导致重大更改的字段名称。
请注意,这是 数据类 由控制器使用 不是 这 数据类 测试使用( TestPostUserRequest) .
结论: 老人 对象映射器 测试成功,就像以前一样,未能识别出重大变化。但是,那 字符串字面量 测试和新 对象映射器 用自己的测试 数据类 也失败了,所以它识别出一个重大变化!完美的。
结论
我希望您也得出这样的结论:在实现细节上测试行为是一个好主意。我们不想将我们的测试绑定到我们的内部视图逻辑( 数据传输对象) 或者 数据库实现 或任何其他实现细节。我们只是想验证,在这种情况下,API 可以在特定的输入和输出下正常工作。所以我们把它当作一个黑匣子。通过一个简单的实验,很容易确定这是否在您的应用程序中正确应用。做一些重构,记住只改变 不改变功能的操作 如果测试写得好,它们都会成功而无需更改它们!
推论:海伦定律
拥有足够数量的 API 用户,
您在合同中的承诺无关紧要:
系统的所有可观察行为
将被某人依赖。
通俗地说这是M 乌菲定律 应用于 API。因此,我们对 API 所做的任何更改都可能会给我们的一些客户带来意想不到的问题,即使我们认为是非破坏性更改,就像前面在场景中提到的那样。这对我们的目的意味着我们希望我们的测试能够捕获尽可能多的变化。我们已经看到了 对象映射器 测试无法识别更改,因此没有帮助。我们能做的,就是让 字符串字面量 测试甚至更好,至少有两种方式:
- 验证完整的响应正文,而不仅仅是特定字段。因此,我们也认识到新领域,一些客户确实在它上面打破了。
- 使用真实数据。通过将测试限制在返回的字段而不是实际数据上,我们仍然可以破坏客户端;正如我们在其中一种场景中使用日期解析器看到的那样。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明