前端测试——从概念和术语说起
从今天起开始梳理前端工程化和自动化相关的一些知识,先从测试相关的部分写起。当然在正式使用工具编写前端测试之前,我们有必要先了解测试究竟是什么,写测试的目的等等相关的背景知识,第一篇文章就从这里说起。
测试是什么
很显然,即使是再厉害的程序员也不可能保证自己编写的代码完全按照预期进行,换句话说就是bug是必然存在的东西,但是在正式部署到线上环境的时候肯定是不能有bug的,所以在部署和完成开发之间,就有一个测试的流程来保证交付代码质量。
之前的刀耕火种年代,测试可能就是人工去试用我们编写出来的软件,看看是否各个功能都能正常按预期运行,如果发现问题就向开发报bug,但人工总是会有疏漏的地方,总是不如代码可靠,于是现在的测试都是再写一些验证代码的代码去验证预期设计。目前测试代码的编写主要按开发理念分为如下两类:
BDD(行为驱动开发)
这种开发理念基于我们的需求行为和需求文档,因此测试代码验证的原子性也应该基于需求,具有如下特点:
- 从业务逻辑的角度切割测试用例
- 使用自然描述语言描述测试用例的目的
- 输出需求期望的测试结果,尽可能的交付出符合需求期望的产品
简单来说就是一切围绕需求去编写测试代码,这类测试其实和人肉测试的思想是一致的,只要需求实现即可,区别在于用代码去代替了人肉部分。
TDD(测试驱动开发)
这种开发理念基于测试角度,因此测试代码的原子性就不再是基于需求了,而是在需求的基础上进行分割,抽象出可验证输入输出的接口代码,再基于此接口的测试代码。测试原子性由需求转为了接口,这种理念的开发流程大致如下:
- 首先基于需求抽象出接口
- 基于接口编写测试
- 所有测试失败,因为没有具体实现
- 基于测试实现业务逻辑代码
- 所有测试通过
- 开发完成
这种开发理念的好处很明显,在需求之下再进行了一层抽象,更有助于我们减少代码逻辑的错误并且让bug在代码层面更加有迹可循。但缺点有很明显,那就是需求之下的这一层抽象,在模块复杂度和依赖过高的情况下不一定容易实现。
总的来说,两种理念各有优劣,TDD的测试代码维护成本相对于BDD要更高些,针对不同复杂度的项目也要考虑引用测试代码的收益是否大于成本。
测试分类
测试按照测试范围从小到大可以笼统的分为如下三类:
- 单元测试:测试一个最小单元
- 集成测试:测试多个单元,但不是全部单元
- 端到端测试:测试所有单元,也就是整个应用
单元测试
单元测试就是以代码单元为单位进行测试,如果说把整个应用看做生物,单元可以看做骨架,通常指的是一个函数,一个模块或者一个类,并且没有I/O和UI的依赖。换句话说,每个单元都是相互独立的,并且相当的具体和灵活,如果某个单元测试失败我们能清晰的知道为何失败。
简单来说,良好的单元测试可以帮我们检查整个应用的细节,不仅可以避免bug,还可以保证优化或重构代码时更加的循序渐进。
集成测试
实际上并不是所有代码都能被抽出单元测试,比如一些依赖ajax或者localStorage等I/O部分的代码,就是不能被单元测试了。这种时候就需要把这些单元集成到相关的独立模块中,编写测试代码验证模块是否能正常工作,这就是集成测试了。
由于集成测试需要验证多个单元组成的模块,并且在真实的配置测试环境验证模块是否正确运行,这就使得集成测试的编写和维护更加困难,所以在编写集成测试前,应该尽可能遵守下列原则:
- 集成测试应该少于单元测试
- 当打算编写集成测试时,思考是否能优化代码进行单元测试
端到端测试
这是在比集成测试的模块更高层的测试,有时候也被称作功能测试,浏览器测试。对于web应用,端到端测试意味着使用工具模拟浏览器去执行与我们人肉使用应用程序时相同的操作。
事实上端到端测试应该尽可能少的编写,因为其复杂程度比集成测试还要高出几个层次,难以编写和维护,并且还相当的慢,因为要真实的模拟用户。除非某个操作需要手动重复操作多次时并且以上两类测试都不适合时,才应该思考使用端到端测试。
总的来说,测试代码应该以单元测试为主,集成测试和端到端测试都是辅助,应该尽可能少的编写。
测试覆盖率
测试覆盖率顾名思义,表示本次测试中,有多少比例的被测试语句被执行了,而这个比例的度量程度又可以分为很多种。
语句覆盖
这是最常见的覆盖方式了,就是按可执行语句的行数作为百分比度量,这也可以说是被诟病最多的覆盖,就像用代码量统计程序员工作成果一样,只管了数量覆盖而不考虑具体逻辑组合的覆盖,难以发现代码的逻辑问题。
决策覆盖
这种度量方式的依据为每个判定的表达式结果,和条件覆盖的度量方式结合理解更佳。
条件覆盖
这种度量方式的依据为每个判定的子表达式结果。就比决策覆盖多了一个“子”字,效果就不一样了。具体看如下例子:
function test(a, b) {
if (a < 5 || b < 5) {
return 0;
} else {
return 1;
}
}
test(2, 7) // 决策覆盖: true(50%) 条件覆盖: true,false (50%)
test(7, 2) // 决策覆盖:true(50%) 条件覆盖:false, true(100%)
也可以看到条件覆盖不是将每个子表达式的结果进行排列组合而是单独分开考虑。
路径覆盖
这应该是最佳的覆盖率度量方式了,度量依据为是否每一个分支都被执行,这也贴合了我们代码的逻辑。
小结
以上就是前端测试中比较重要的一些概念,之后再结合具体工具梳理实际编写测试用例需要注意的地方。
-- EOF --