搭建基本 Jest 测试框架,解读覆盖率实现原理

前端自动化测试,在写通用库的时候,为了减少后续更新迭代需要的测试投入,以及保证库功能的健壮性,通常都要带上的。当然在实现部分业务代码,可能也会考虑写测试用例,只不过这块的投入成本往往比较大,投入产出性价比不够高,因此一般只会使用在核心功能上。
前端自动化测试一般分为 2 种:单元测试和 e2e 测试(UI 自动化测试)。e2e 测试主流框架有:selenium、cypress、puppeteer 等;单元测试主流测试框架有:Jasmine
、Mocha
、Jest
等。它们都很优秀,易用性也很强,功能也强大。但本文这里只聊聊 Jest 这个框架。
一、Jest 优点
Jest 是 Facebook 的一套开源 JavaScript 测试框架,专注于简洁明快。像Babel
、TypeScript
、Node
、React
等优秀的项目都在使用它。它有几大优秀:
零配置在大部分 JavaScript 项目上实现开箱即用,无需配置。
快照
能够轻松追踪大型对象的测试。快照可以与测试代码放一起,也可以集成进代码行内。
隔离
测试程序拥有自己独立的进程,以最大限度地提高性能。
优秀的 Api
从
it
到expect
- Jest 将整个工具包放在同一个 地方。好书写、好维护、非常方便。支持覆盖率
通过添加
--coverage
标志生成代码覆盖率报告, 无需额外设置。
它的优势非常多,不仅于此,感兴趣的可以前往官网查看。
二、Jest 使用和配置
这里的环境我们按照 TypeScript 进行配置。
安装包
babel
的配置如下,需要注意的是:presets
配置,它的执行顺序是从后往前执行的,如果 babel 有配置plugins
它的执行顺序是从上往下。
配置
npm script
配置到这,基本的 Jest 测试框架环境就好,执行 npm run test
就会执行测试用例了。
三、编写测试用例
如何编写测试用例,已经存在很多教程,官方也有非常多例子,因此这里我们只写两种很常用的:同步代码和异步代码的测试用例。
同步代码
异步代码
关于如何编写更多场景的测试用例,可前往官网查看。下面看看如何解读覆盖率。
四、解读覆盖率
通常写完库的测试用例,需要跑下覆盖率,看看测试用例覆盖率如何,jest
刚好也支持查看覆盖率,对应的指令是:jest --coverage
。当执行该指令,会在当前项目跟目录生成覆盖率文件夹coverage
如下:

找到lcov-report/index.html
文件,然后在浏览器中打开,此时我们便可以查看当前项目的测试用例书写覆盖率了。这里以我写的工具库为例子:

表格中罗列了所有工具类库的测试用例,存在几下几个指标:
Statements 语句覆盖率,它其实对应的就是 js 语法上的语句,js 解析成 ast 数中类型为
statement
。Branches 分支覆盖率,通俗点理解就是
if/else
这类条件Functions 函数覆盖率
Lines 行数覆盖率,就是代码执行了多少行
文件中,我们发现 url-utils
文件中的 Branches 覆盖率不高,才达到64.29%
,那么如何查看具体哪些代码没覆盖到呢?很简单,点击对应的文件名就可以进入查看了,效果如下:

图中黄色块代表的是测试用例没有测试到的方式,当然你也可以按快捷键 n
或者 j
去查看下一个没有被覆盖到的代码块。图中黄块未覆盖到的,刚好也正是条件语句,符合 Branchs 的测试覆盖率。到这里便完成了基本的测试用例编写,以及测试用例生成,并且能够查看测试用例覆盖率如何,以此来完善整体测试用例的编写,帮助代码达到完善健硕的目的。
关于覆盖率是如何生成的,它的原理是怎么样的呢?
五、覆盖率原理
TIP:再进行真正解读原理前,先进行大胆的猜测,然后再去验证它,会比生硬的看原理来得有意义。
通过生成的指标看出 jest
框架生成的覆盖率对语句、函数、分支、行数这 4 个维度进行了生成,如果对 js 编译原理有所了解,根据敏锐度大体能过猜测到应该是需要对 js
做 ast
树解析,因为这几个指标的类型,在对应的 ast
数的节点类型上都有对应的体现。当解析成 ast
树后,应该存在某种机制能往树里面侵入部分统计代码,那么在执行的时候遍可以统计 ast 树哪些类型的节点是已经被执行了。
了解库的实现原理,通常第一步是打开类库的package.json
文件,看看它都依赖了什么。通过对该文件的排查,我们排除到这三个库:

istanbul-lib-coverage
:提供覆盖率信息的只读视图,能够合并和汇总覆盖率信息的 api。istanbul-lib-report
:istanbul 库生成报告的核心程序。istanbul-reports
:大体提供报告输出的公共能力 api。
这三个库使用围绕着 istanbul
,因此该库基本大概率提供了核心的覆盖率能力。
1、istanbul
istanbul,为 ES5 和 ES2015+JavaScript 代码插入行计数器,这样您就可以跟踪单元测试对代码库的运行情况。
安装使用
安装完成后,执行 istanbul help
能够获取它所支持的命令:
check-coverage
:根据 JSON 文件的覆盖率阈值检查总体/每个文件的覆盖率。如果不满足阈值,则退出 1,否则退出 0。cover
:透明地将覆盖率信息添加到节点命令。在执行结束时保存 coverage.json 和报告。instrument
:插入文件或目录树,并将插入指令的代码写入所需的输出位置。report
:为上一次运行中生成的 JSON 对象写入覆盖率报告。
命令中 instrument
名为 “插桩”,跟猜想功能类似。
2、instrument 命令
该指令会往执行代码插入统计辅助代码,该行为称为:函数插桩
。
下面验证下:
对 test.js
文件执行该命令,并输出到test-inst.js
文件:
转换后的代码,通过格式化后如下:

可以看出如下信息:
它创建了一个全局对象用于存储统计信息
它往函数执行代码各个对应位置插入了统计代码,对应代码块执行就
+1
命名很复杂,应该是为了防止与命名冲突。
这是生成覆盖率的基本原理。那至于 istanbul
库是如何实现函数插桩功能的呢?
3、函数插桩原理
通过 istanbul
库的 package.json
文件,找到两个重要的库:
escodegen:它是来自 Mozilla 解析器 API AST 的 ECMAScript(也称为 JavaScript)代码生成器
esprima:是一种高性能、标准兼容的 ECMAScript 解析器
同时在源码中可以找到插桩器实现 Instrumenter
,它的代码在:lib/instrumenter.js
文件。
下面看下主体流程:
当执行
instrument
命令,实际上会实例化一个Instrumenter
,在内部会挂在个this.walker
它的作用是,声明需要执行函数插桩的 AST 节点类型,以及对应的插桩函数,比如:this.coverStatement
。

执行它的
instrumentSync
函数,通过 esprima 将代码解析成 AST 树,后进行函数插桩操作。

声明
coverState
对象存储覆盖率数据,并对AST
树进行统计代码插桩操作,并将统计coverState
转换为 JSON 字符串输出。
具体的插桩操作
在执行 this.walker.startWalk
时,执行节点的插桩函数 this.coverStatement
嵌入统计代码。

将覆盖率数据转为字符串输出,这就是前面执行
instrument
命令代码进行插桩后,头部声明的数据。

以上是整个覆盖率原理解读和主体核心流程代码,加而言之就是:将代码转为 AST 树,然后对需要统计覆盖率的 statement/branch/function 等对应的节点类型进行函数插桩操作用于统计对应代码的执行率。
六、总结
通过对 Jest 测试框架的基本搭建,以及解读覆盖率报告和它的实现原理,基本能掌握该框架核心的机制,对于更细致的业务代码测试用例,使用的时候再去找对应的 API 进行学习使用也是可以。
版权声明: 本文为 InfoQ 作者【梁龙先森】的原创文章。
原文链接:【http://xie.infoq.cn/article/f743bf48dc9ea09e5cbd7285c】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论