单元测试
基本概念
- 英文 Unit Testing,又称为模块测试。
- 单元测试本质上也是代码,与普通代码的区别在于它是验证代码正确性的代码。
- 单元测试是由开发人员编写的、用于检测在特定条件下目标代码正确性的代码。
- 单元测试是针对程序模块(软件设计的最小单位。面向过程编程(OPP)中,一个单元就是单个程序、函数、过程等;面向对象编程(OOP)中,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。)来进行正确性检验的测试工作。
- 单元测试允许程序员在未来重构代码,并且确保模块依然工作正确(复合测试)。在代码变更导致错误时,程序员可以借助单元测试快速定位并修复错误,因此单元测试的编写需要良好的设计性与可读性。
- 单元测试可以自动进行也可以手动执行,虽然 IEEE 并没有明确表示偏爱哪一种方式,但是自动进行的单元测试能更好地节省开发人员的时间与精力。
- 极限编程(Extreme Programming,XP)提倡在功能开发前先进行单元测试,以激励程序员设想出代码会在何种条件下出错的种种可能情况。极限编程认为当程序员无法再想出更多能使代码出错的情况时,代码才算真正意义上的完成。
作用
- 便于后期重构。单元测试可以为代码的重构提供保障,只要重构代码之后单元测试全部运行通过,那么在很大程度上表示这次重构没有引入新的 BUG,当然这是建立在完整、有效的单元测试覆盖率的基础上。
- 提供优化设计的思路。编写单元测试将使用户从调用者的角度观察、思考,特别是使用 TDD 驱动开发的开发方式,会让使用者把程序设计成易于调用和可测试,并且解除软件中的耦合。
- 具有文档记录的功效。单元测试就是一种无价的文档,它是展示函数或类如何使用的最佳文档,这份文档是可编译、可运行的、并且它保持最新,永远与代码同步。
- 具有回归性。编写完成之后,可以随时随地地快速运行测试,而不是将代码部署到设备之后,然后再手动地覆盖各种执行路径,这样的行为效率低下,浪费时间。
影响
- 给开发人员带来自信。在接手一个新的项目,或者说是参与一个新的项目开发时,往往这种情况是你半途参加进去的,你需要对已有的代码结构进行解读和理解,对于业务的理解,对于代码个中各个模块关系的理解。如果一开始就理解出错,就算是简单的修改也很可能引起更多的 BUG 出现,到那时候又需要修复更多的 BUG,改了一个地方,很有可能会莫名其妙地影响另外一个地方,这种现象是很常见的。还有一种情况,假设你修改的功能没问题,但是需要去测试验证,在测试时就需要考虑这个功能点它原有的测试路径有哪些,又需要一一去验证功能路径,以证明本次修改对于已存在的功能点不造成影响。这其中就存在着很大的时间成本,导致效率不高。那是否存在这么一种方式,开发人员需要修改其想改动的地方,不需要关心修改完之后它所造成的影响,也不需要关心它的测试回归性,答案是有的,此时就是单元测试登场的时候。写单元测试代码,可以让开发人员写的代码足够自信,因为它是经得起考验的。
- 能提供更快的反馈。对于有一定编程经验的开发人员来说,当其遇到一个新需求时,首先想到的不是动手 Coding ,而是会先想想代码的结构,有些类,数据结构当是如何,然后才开始敲代码。如果没有单元测试,一般流程基本是这个模块功能全部写完才开始测试,比如典型的 MVC 架构,通常是 Model ——> Controller ——> View 这样一个流程,只有这个流程全部结束了才开始验证写的功能模块是否符合需求,如果没有符合则需要返回去修改代码,这中间需要花费很长的时间才能知道当下自己写的代码是否符合要求,是否正确。那有没有一种即时反馈的方式呢,答案依然是有的,写单元测试即可,当你写完一个函数,马上就匹配一个单元测试函数,这样即写即测的方式可以保证你当场写的代码可以马上进行修改,测试通过一个,就表示完成一个小的功能点,直到最后把函数组装起来,我们要的功能点也就完成了。如果担心新添加的功能点对其他功能点有影响,我们只需要跑一遍完整的单元测试就好了,如果出现的错误是新功能点引起的,修改好让其再次顺利通过即可,如果原来的单元测试本身就存在问题,这样就需要找到相关的负责人来一起解决,避免出现片面的功能点理解,这便是极限编程中的结对编程希望看到的结果。
- 能为开发人员节约大量的时间。当然在项目伊始可能不是节省时间而是耗费时间,因为进行单元测试可能需要依赖于第三方框架,我们需要进行环境的搭建与熟悉代码的编写等额外的工作,这也是单元测试为什么没有得到很好的推广的一个重要原因,这部分的时间与金钱的投入使很多的项目负责人望而却步甚至直接将单元测试放弃了,因此这里所说的能为开发人员节省大量的时间是从长远来说的。对于一个存在已久的大型项目来说,不管是添加新功能还是进行重构,不可避免地需要验证新功能或者重构是否会对原有功能造成一些破坏性的影响,如果没有单元测试的保障,就需要重复性地一遍遍执行整个流程来进行验证,这里面将要花费的时间可想而知。如果绝大部分的功能在单元测试阶段就能验证完毕,那么速度就相对快很多。此外,单元测试还能帮忙减少 BUG ,从而减少调试 BUG 的时间,一些低级犯的错误在单元测试阶段就能避免掉。
第三方框架
Jest
Jest 是 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架。其具有以下特点:
- 零配置——Jest 的目标是在大部分 JavaScript 项目上实现开箱即用,无需配置。
- 快照——构建能够轻松追踪大 Object 的测试。快照可以独立于测试代码,也可以集成进代码行内。
- 隔离的——测试程序在自己的进程并行运算以最大限度地提高性能。
- 优秀的 api——从 it 到 expect - Jest 将整个工具包放在一个地方。好书写,好维护,非常方便。
- 快速且安全——通过确保你的测试具有独一无二的全局状态,Jest 可以可靠地并行运行测试。 为了让加速测试进程,Jest 会先运行先前失败的测试,并根据测试文件需要多长时间重新组织测试。
- 代码覆盖率——通过添加--coverage 标志生成代码覆盖率报告,无需额外设置。Jest 可以从整个项目收集代码覆盖面信息,包括未经测试的文件。
- 轻松模拟——Jest 支持针对你的测试文件引用使用使用自定义解析器,使得模拟任何超出您测试范围的对象变得简单。 您可以用功能齐全的Mock 功能API 来使用模拟导入函数和可读测试语法进行调用.
- 优秀的报错信息——测试失败——当测试报错时,Jest 会提供丰富的上下文内容。
Mocha
Mocha 是一个功能丰富的 javascript 测试框架,运行在 node.js 和浏览器中,使异步测试变得简单有趣。Mocha 测试连续运行,允许灵活和准确的报告,同时将未捕获的异常映射到正确的测试用例。使用 Mocha,我们就只需要专注于编写单元测试本身,然后让 Mocha 去自动运行所有的测试,并给出测试结果。
- 功能丰富。
- 使用灵活。
- 支持异步。
- 需要额外的断言支持。
- 不仅允许运行所有的测试,也允许执行特定的测试。
- 支持钩函数,可以支持 before、after、beforeEach 和 afterEach 来编写初始化代码。
Jasmine
Jasmine 是一个行为驱动的开发框架,用于测试 JavaScript 代码。它不依赖于任何其他 JavaScript 框架。它不需要 DOM。而且它的语法简洁明了,因此您可以轻松编写测试。
- 开销低,无需额外的依赖。
- 开箱即用,提供测试代码所需的一切。
- 支持浏览器测试与 Node.js 测试。
AVA
AVA 可以帮助您完成复杂麻烦的测试。AVA 是 Node.js 的测试运行程序,具有简洁的 API,详细的错误输出,新的语言功能和流程隔离功能,可让您更有效地编写测试。因此,您可以通过 AVA 发布更出色的代码。
- 异步,性能好。
- 轻量,高效,简单。
- 快照测试和断言需要三方支持。
- 并发测试,强制编写原子测试。
- 没有隐藏的全局变量,每个测试文件独立环境。
- 内置断言,强化断言信息。
- 可选的 TAP 输出显示。
Tape
- 体积最小,只提供最关键的东西。
- 对比其他框架,只提供最底层的 API。
- 支持浏览器测试与 Node.js 测试。
如果你希望得到很好的社区支持与灵活的个性化配置测试,Mocha 将是最好的选择;如果你希望不仅开箱即用又能提供全面的测试方案,Jest 将是不错的选择;如果你希望最精简的测试功能,Tape 将是你的最爱。