在 1998 年,Kent Beck 编写了 sUnit,一个面向 SmallTalk 的单元测试框架。之后,他将这个框架移植到 Java,即 jUnit。从那时起,xUnit 框架扩展到那些最流行的编程语言。比较新的语言,如 Golang 和 Rust,已经将测试直接合并到编译器和标准库中。
但是单元测试并不是唯一。还有集成测试和性能测试等等。在我看来,集成测试和单元测试是健壮软件的基石。因此,今天让我们看看单元测试与集成测试之间的区别,以及你什么时候该选择哪种测试。
一个单元是逻辑上分离的最小代码块
单元测试是一种孤立地测试尽可能小的代码片段的测试。那么,什么是一个单元?
术语“单元”来自数学。数字 1 被认为是单元,因为它是最小的自然数。它是最小的正整数。以此类推,你源代码的一个单元就是逻辑上与其余代码分离的最小代码片段。它是一个完整的且逻辑上不同的代码片段,而且是最小的部分。
在大多数编程语言中,你的单元会是一个函数或方法调用。
单元测试的好处是,如果你的代码由独立的小片段组成,那么,为它们编写测试就相当容易。这种易编写性意味着你可以在开发功能时完成单元测试。
与其它形式的测试相比,单元测试的执行时间相当短。这意味着你可以频繁运行单元测试。随着软件的成熟,一套单元测试是防止回归和降低维护成本的有力工具。
在考虑将单元测试添加到现有软件时,需要考虑成本和收益。
单元测试的一个关键假设是,被测试的软件很容易分成不同的单元。在没有考虑单元测试编写的软件中,这个假设很少成立。向现有软件添加单元测试通常是一种非常好的方法,来稳定软件并防止将来回归,但是重构代码来支持简单的单元测试可能需要大量工作,甚至会引入新的缺陷。在考虑将单元测试添加到现有软件时,需要考虑成本和收益。如果你的代码正在工作,如果代码很少需要修改,如果代码不容易进行单元测试,那么加入单元测试的好处可能无法保证成本。在这些情况下,可以依靠集成测试来防止该领域的缺陷。
集成测试聚焦于整个软件栈
如果单元测试的哲学是基于这样一种认识,即测试小的独立代码片段是防止回归的一种好方法,那么集成测试是基于这样一种理解,即事情通常在边缘状态出错。外部世界是一个混乱的地方,它与你代码交互的地方通常是意外发生的地方。
你可以通过单元测试实现 100%代码覆盖率,但仍然发现你的软件失败。你可能试图从错误的位置读取文件,或者你的软件可能从一个调用的服务得到预期之外的输出,或者它可能以一种无效的方式调用数据库。
尽管单元测试应该快速运行并且数量众多,但是一个好的集成测试策略应该关注较少数量的高影响测试。
这些测试应该跨越单元测试无法跨越的所有界限,写入文件系统,接触外部资源,等等。
某些外部系统确实很难集成到测试中。这是因为它们在现实世界中有着无法消除的副作用:金融交易、电子邮件发送、物理移动一个喷漆机器人等。在你在测试中放弃并避开它们之前,找找解决方案。
许多外部系统有一个文档化的方法来在集成测试中使用它们。支付处理程序通常有测试信用卡号,可以设置具有测试邮箱账户的测试用户来测试邮件发送。
集成测试越接近真实世界的交互,就越有可能发现问题并提供真正的价值。
Amazon SES——Test email addresses
Paypal——Test credit card numbers
UPS——Test api mode
假设你正在编写一个简单的电商网站,一个简化版的 amazon.com。这里的细节很重要,所以我们假设,你会使用 PostgreSQL 作为你的数据存储,使用 PayPal 进行支付,使用 UPS 进行发货、使用 Amazon Simple Email Service 来发送电子发票邮件。
单元测试策略将以一种孤立的方式测试应用程序的逻辑。这可能包括:
这些领域中的每一个都可能有几个测试。每个测试将验证一小部分功能。单元测试的能力来自它们的数量、简单性以及它们的执行速度和便捷性。
另一方面,你的集成测试将专注于测试你的电子商务代码与其它系统的交互。这意味着不仅要测试与数据存储的集成,还要测试与邮件发送服务的集成、与支付服务的集成等等。这些可能包括:
这些功能中的每一个都可能需要一个或两个集成测试来验证。这些测试运行起来会比较慢,可能涉及一些安装和拆卸步骤。结果是,每个测试的代码覆盖率会相当大。这些测试将通过捕获单元测试不能捕获的问题来产生价值。然而,维护成本和执行时间可能会比较高。
是时候正面比较了
那么,应该首选哪种类型的测试呢?单靠两者中的任一个都是不够的。这两者都是综合测试计划的一部分。让我们直接比较一下:
每种情况都是独特的,基于在其它情况下有效的建议不应盲目遵循。
现在我们明白了,单元测试不应该触及文件系统,而集成测试应该只集成松散的组件。但实际上,将测试划分为两个明确的类别有点太简单了,如果我们只关注定义,我们就会忽略目标,即正确的工作软件。
一些非常有想法的开发者认为单元测试可以并且应该读写数据库。其它人则认为单元测试是一种浪费,粗粒度的集成测试提供的价值最大。
问题是,每种情况都是独特的,基于在其它情况下有效的建议不应盲目遵循。需要牢记的一个问题是,这个测试要捕获什么类型的缺陷。如果每个测试都是经过深思熟虑编写来提升软件可靠性的,如果测试在不再有价值时被删除,那么随着时间的推移,将发现为特定项目提供最大价值的特定测试方法。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货