Logo
Section 1 / 15

单元测试简介

了解 Elixir 中的单元测试。

导入

单元测试简介

了解 Elixir 中的单元测试。

对于大多数工程师而言,测试体验通常从单元测试开始。单元测试之所以最容易编写,原因如下:

  • 聚焦范围有限。
  • 比其他类型的测试更简单。
  • 构成了所有测试的基础。

在本章中,我们将学习如何定义测试的范围,为功能代码编写测试,并组织测试文件。随后,我们会探讨一些隔离被测代码的方法。

定义单元

单元测试是一种专注于应用程序某一部分的软件测试,通常会移除代码使用时的整体上下文。

常见的做法是将一个单元测试聚焦于单个函数及其内部逻辑,可能还包含该函数可访问的私有函数。有时,将多个模块或处理流程的代码纳入单元测试的范围更有意义,从而扩大了单元的定义范围。

为什么要进行测试?

在设计测试时,我们需要牢记四个目标:

  • 证明我们的代码确实实现了预期功能。
  • 防止引入破坏性变更(回归)。
  • 便于快速定位代码出错的位置。
  • 尽可能减少测试代码的编写量。

前两个目标较为常见,而后两个目标将指导我们决定测试范围内应包含多少代码作为“单元”。

我们的测试应在被测代码周围形成一个黑盒,我们称之为“被测代码”或“应用代码”。这个黑盒定义了测试代码与应用代码之间的交互点。在单元测试中,这个黑盒通常将单个函数视为一个单元。

...
测试范围

保持测试范围狭窄有利有弊。

优点缺点
快速定位问题所在需要编写更多测试代码来隔离被测代码

如果允许测试范围(黑盒)包含已充分测试且功能正常的依赖项,我们可以在享受窄范围优势的同时,避免部分隔离工作的复杂性。

隔离被测代码

我们的测试需要以两种方式与被测代码交互:

  • 测试会调用某个函数(或发送消息),传入参数并获取返回值。
  • 测试可能需要拦截对外部调用的请求,并返回模拟响应,代替实际依赖项,特别是当我们的代码依赖于代码库中的其他部分时。

第二种交互更为复杂,称为隔离被测代码。

我们将在课程后续部分详细讲解如何隔离代码,但首先,让我们看看在什么情况下可以不必严格隔离。下图展示了围绕被测代码绘制的黑盒:

...

由于被测代码依赖于代码库中的其他代码,测试必须处理隔离的问题。虽然存在多种隔离方法,但它们通常伴随着代价,主要体现在时间和测试复杂度上。纯函数式代码的存在会影响需要隔离的内容。

纯函数的特性

如果一个函数在每次使用相同参数调用时都返回完全相同的值,并且没有任何副作用,则称其为纯函数。

扩展黑盒范围

当我们的依赖项是纯函数且已独立经过充分测试时,我们可以将该依赖项纳入黑盒范围,如以下示意图所示:

...
测试范围

扩展黑盒范围使我们能够省去额外的隔离工作,同时仍能轻松定位问题代码。测试依然聚焦于被测代码本身,而由于被测代码是纯函数,测试环境完全可控,避免了结果不一致的问题。

通过这种方式包含依赖项的好处包括:

  • 测试更容易编写、理解和维护。
  • 编写的测试代码更少,但仍能在测试失败时准确指出问题所在。
  • 如果依赖项自身的测试未失败,但我们在引入该依赖项的测试中出现失败,说明问题出在新代码中,而非依赖项本身。

注意: 关键在于把握何时应将纯函数式依赖项纳入测试范围。这种判断需要通过不断尝试和经验积累来掌握。一种有效策略是先将黑盒范围扩大以包含这些依赖项,若发现调试困难再逐步收紧范围。随着实践增多,我们会逐渐培养出对这一决策的敏锐直觉。

入门

我们的第一个测试