服务调用使您的测试效果更好
本文最初发表于JustinDFuller.com。
TL;DR:如果所有测试都是模拟的,你就不知道你的代码是否真的有效,你只知道,理论上,如果集成符合你期望的约定,它应该有效。
模拟、桩代码,或者——或许更好的选择——依赖倒置,这些方法可以简化测试,使代码更容易修改,但它们也会带来问题吗?让我们来看看。
请看这个测试,我们正在使用外部文件服务保存文件。
你能判断一下这个测试是否有效吗?
答案是它很可能永远都抓不到任何虫子。我们来看看为什么抓不到。
第一个原因是,我们使用的是网盘服务的模拟实现。因此,如果有人在不更改文件上传器的情况下更改了网盘服务,我们将无法捕获任何错误。
那么,如果未来情况更糟呢?如果我们想要集成的实际驱动器服务器发生变更怎么办?我们肯定无法捕获这些错误,因为我们根本没有调用过它。
最终,我们真正测试的只是 uploadFile 方法是否将文件对象传递给 driveService 的 write 函数。如果 driveService 错误地使用了文件对象,或者 fileUploader 停止直接将文件传递给 driveService,我们都会捕获到错误。
不幸的是,每当我们更改 fileUploader 和 driveService 的交互方式时,我们也必须更新此测试。
所以,这个测试不仅脆弱,而且没什么用。此外,在类型化语言中,它更是完全没用,因为编译器会在编译过程中捕获这类错误。
那么,我们该如何改进这个测试呢?如果测试能够真正向硬盘服务器发出服务调用,而不是调用内部的硬盘服务对象,那么它就能发挥作用了。
你可能会立刻提出异议:“如果我进行实际的服务调用,我的单元测试运行时间会非常长,而且会变得极其脆弱!” 如果你这么想,那就完全正确。这个测试最好作为集成测试。
集成测试
集成测试的运行频率可能不如单元测试,但至少应该在将更改集成到代码库之前运行。明白我的意思了吗?集成测试会在集成时运行。
然而,问题依然存在:我该如何切实可行地运行应用程序所需的所有服务器?启动服务器的成本可能很高,更不用说数据存储、端口管理、身份验证以及构建一个功能齐全的系统所需的其他一切了。
请看这个简单的系统图。它代表了之前示例测试中系统的简化版本。
您可以看到,我们在这里只关注测试我们的“文件服务”与属于另一个团队的外部“云端硬盘服务”的集成。在这种情况下,我们并非要进行完整的端到端测试。
但我们实际测试的是什么?
糟糕!这里(绿色部分所示)只测试了文件服务。我们想测试的是文件服务及其与云端硬盘服务的连接是否正常工作。因此,我们不会自己编写模拟版本,而是会想办法获取一个可测试的云端硬盘服务版本。
独立服务
一种方法是创建一个隔离的 Drive Service 版本。理想情况下,这个版本应该由 Drive Service 的创建团队拥有。为了确保这个模拟服务器的可信度,他们将对模拟服务器和真实服务器运行相同的测试。
以上代码展示了一个隔离服务器的示例实现。可以看出,它与实际服务器实现非常相似,区别在于它使用内存数据存储而非实际的文件存储设备。它甚至使用了端口 0 来确保使用临时端口,从而进一步提高了测试的稳定性。
现在 Drive 团队提供了这台隔离服务器,我们的集成测试就可以安全地启动它并在测试过程中使用它了。让我们把原来的测试重写成集成测试,看看它是否更有用。
现在,我们的测试更有用了吗?由于我们调用了真正的 Drive Server API(即使保存到不同的存储设备,API 和业务逻辑也保持不变),我们将知道我们的集成是否出现问题。
更棒的是,我们不仅可以测试它返回的URL,还可以测试内容是否按预期保存。我们的测试将真正告诉我们文件保存是否有效!
请再次查看我们的系统图。您可以看到用绿色标出的正在测试的服务。这次我们测试的是文件服务、驱动器服务,以及最重要的,它们之间的连接。
期望每次修改一行代码都运行这组测试是不现实的——这种期望应该留给单元测试——但这个测试仍然足够轻量级,可以在每次代码提交时运行。以这种方式执行集成测试,可以确保你的主分支不仅拥有正确的业务逻辑,还能与其他服务实现有效的集成。
当无法提供独立服务时,可以采取备用方案。
有时,某些环境或构建时的限制确实会导致无法使用隔离服务器。在这种情况下,您可以考虑使用模拟 API 作为备选方案。
请记住,我们这里讨论的仍然是集成——即与其他服务交互的代码。您可能已经注意到,代码中包含两个与 Drive 相关的实体:“DriveServer” 和 “DriveService”。DriveServer 是属于第三方的实际服务。我们使用他们的内存版本来测试与他们服务的集成。DriveService 是一个 API,它知道如何与 DriveServer 交互。这个 API 也属于 Drive 团队。
值得庆幸的是,他们明白并非所有人都能使用他们独立的内存服务器,所以他们也创建了一个模拟版本的 API。请看。
这个 FakeDriveService 是 Drive 团队可以提供给所有使用其服务的用户的一个实现版本。他们表示:“如果您使用 FakeDriveService 进行测试,就可以相信真正的 DriveService 也能正常工作。我们对两者都进行了测试,以确保它们运行一致。”
这种实现方式显然比独立服务器还要轻量级,那么缺点是什么呢?让我们再次参考一下系统图。
虽然我们目前只是在技术上测试连接机制,但实际上并没有触及 Drive 服务本身。我们的测试基于信任,而非实际验证。这种信任源于我们假设模拟服务确实与完整服务运行方式相同。在许多情况下,这已经足够,但如果您拥有生产关键系统,则可能需要更可靠的保障。
不过,这个测试比我们最初使用的模拟函数要好得多。那个模拟函数完全不可信,甚至可以说它欺骗了我们,让我们误以为代码不会出现 bug,但实际上我们对此一无所知。如果 Drive Service 发生变化,我们的测试就必须随之修改。在最初的模拟场景中,我们无需修改测试,因为它会让我们误以为代码仍然有效,即使它由于 API 的更改而失效。
鸣谢
这篇文章的灵感直接来源于我最近在谷歌“厕所测试”博客上看到的几篇文章。我想借鉴他们的想法,并将其重新诠释为一个 JavaScript 应用。请点击下方链接阅读他们的原文。
演习服务呼叫
https://testing.googleblog.com/2018/11/testing-on-toilet-exercise-service-call.html
假货
https://testing.googleblog.com/2013/06/testing-on-toilet-fake-your-way-to.html
密封服务器
https://testing.googleblog.com/2012/10/hermetic-servers.html
免责声明
本文观点和建议仅代表我个人,与我的雇主无关。我无意通过此文代表他们。
联系我们
我很乐意收到你的来信。请随时通过Github或Twitter联系我。
这是转载,原文发表于www.justindfuller.com。
文章来源:https://dev.to/justindfuller/service-calls-make-your-tests-better-2eck