前端自动化测试 —— Jest 测试框架应用
http://zoo.zhengcaiyun.cn/blog/article/jest
什么是自动化测试
在软件测试中,自动化测试指的是使用独立于待测软件的其他软件来自动执行测试、比较实际结果与预期并生成测试报告这一过程。在测试流程已经确定后,测试自动化可以自动执行的一些重复但必要的测试工作。也可以完成手动测试几乎不可能完成的测试。对于持续交付和持续集成的开发方式而言,测试自动化是至关重要的。 ——来自 WiKi 百科
为什么要用前端自动化测试
随着前端项目的发展,其规模和功能日益增加。为了提高项目的稳定性和可靠性,除了需要测试工程师外,前端自动化测试也成为了不可或缺的一环。采用前端自动化测试可以有效地提高代码质量,降低出错的概率,从而使项目更加健壮和更易维护。
前端自动化分类和思想
单元测试
又称为模块测试 ,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。在前端中,一个函数、一个类、一个模块文件,都可以进行单元测试,测试时每个模块都是互不干扰的。
集成测试
是在单元测试的基础上,测试再将所有的软件单元按照概要设计规格说明的要求组装成模块、子系统或系统的过程中各部分工作是否达到或实现相应技术指标及要求的活动。用户的开始操作到结束操作这一整个行为流程可以当作集成测试。
TDD 测试驱动开发(Test Driven Development)
开发流程:
TDD 是趋向于白盒测试,需要开发者对当前编写的模块思路足够清晰。
优势:
- 长期减少回归 bug。
- 代码质量更好,可维护性高。
- 测试覆盖率高(先写测试用例,再实现功能)。
- 错误测试代码不容易出现(测试在开发之前执行)。
BDD 行为驱动开发(Behavior Driven Development)
开发流程:
BDD 趋向于黑盒测试,只关注用户的一整套行为流程下来是否会成功。
优势:
- 对于用户行为的整个流程把控程度较高,对于开发人员来说这样安全感高。
如何自己写非框架测试用例
不使用测试框架,我们该如何测试自己的模块呢?如果我们想要测试下面的代码,应该需要两个值,一个是 期望值 ,另一个是函数执行的 结果值 ,我们需要对比两个值来进行判断当前函数是否通过了测试用例。
需要下面的 if / else 进行判断当前的期望值 value 和结果值 result 是否相等,如果相等说明我们的测试用例通过了。我们将这两段代码复制到浏览器中,下面的执行不会通过,并会抛出错误,只有我们将传入值改为 ZooTeam 才会成功执行。
是否能简化?
如果我们有多个函数需要测试,你应该不想写许多个 if / else 代码块吧?所以我们要将上面的代码块进行优化成一个函数。
经过上面的封装,我们就可以只写一行代码进行测试了!
如何能清晰地看到我测的是哪个呢?
虽然上面的封装只需要书写一行代码就可以测试了,但是我们不知道执行结果和测试用例之间的对应关系,我们需要输出的文字来告诉我们当前是哪个测试用例执行了。
成功和失败都会进行提示,这样我们就可以知道当前是哪个测试用例成功/失败了。
Jest 的书写方式也是同上,如果上面的一整套代码了解了的话,你已经可以写 Jest 的测试脚本了,下面将进入 Jest 的配置。
如何使用 Jest 测试框架进行自动化测试?
主流的前端自动化测试框架
Jasmine
Jasmine 优点:易于学习和使用,支持异步测试,可以在浏览器和 Node.js 环境中运行,可以生成易于阅读的测试报告,可以与其他库和框架集成。
MOCHA
MOCHA 优点:支持异步测试和 Promise ,可以在浏览器和 Node.js 环境中运行,可以与其他库和框架集成,可以生成易于阅读的测试报告,可以使用各种插件和扩展来增强其功能。
Jest
Jest 是针对模块进行测试,单元测试对单个模块进行测试,集成测试对多个模块进行测试。
Jest 优点:速度快(单模块测试时,执行过的模块不会重复执行),API简单,易配置,隔离性好(执行环境相对隔离,每个文件单独隔离互不干扰),监控模式(更灵活的运行各种测试用例),适配编辑器多,Snapshot(快照),多项目运行(后台前台测试用例并行测试),生成可视化覆盖率简单,Mock 丰富。
准备工作 —— Jest 的配置
以上方法执行结束后,会生成一个 jest.config.js 文件,里面包含了 Jest 的配置项,每个配置项都会带有描述,在初始化的两个配置也会体现在配置文件中。
使用 babel 转换来使用 ES6 形式的导入和导出
配置好后需要将 package.json 中的 test 命令的 value 改为 jest --watchAll ,代表监听所有有修改的测试文件,然后控制台执行 npm run test 就可以执行测试用例了。
Jest 启动时会进行如下流程
- npm run test
- jest (babel-jest) 检测当前环境是否安装了 babel
- 如果安装了则会去 babelrc 中取配置
- 取到后执行代码转换
- 最后再执行转化过的测试用例代码
如何生成一个测试用例覆盖率报告?
经过上面的 Jest 配置,我们就可以通过下面的 npx 命令来生成测试覆盖率报告了。
会生成一个名为 coverage 的文件夹,打开里面的 html 就可以看到你的覆盖率,其中 Statements 是语句覆盖率(每个语句是否执行),Branches 是分支覆盖率(每个 if 块是否执行),Functions是函数覆盖率(每个函数是否执行),Lines 是行覆盖率(每行是否执行),通过修改 coverageDirectory 的值可以改变测试覆盖率生成文件夹的名字。
Jest 基础匹配器
上面我们说过了,Jest 的用法和我们封装的那几个函数是一样的,都是执行 test 函数并向函数中传递参数,第一个参数是你当前测试用例的描述,第二个参数是需要执行的匹配规则。
匹配器
toBe
toBe 匹配器,期待是否与匹配器中的值相等 相当于 object.is ===
toEqual
toEqual 匹配器,只会匹配对象中的内容是否相等。
toBeNull
toBeNull 匹配器,可以判断变量是否为 null ,只能匹配 null。
toBeUndefined
toBeUndefined 匹配器,可以判断变量是否为 undefined ,只能匹配 undefined。
toBeDefined
toBeDefined 匹配器,希望被测试的值是定义好的。
toBeTruthy
toBeTruthy 匹配器,可以判断变量是否为真值,会对非 bool 值进行转换。
toBeFalsy
toBeFalsy 匹配器,可以判断变量是否为假值,会对非 bool 值进行转换。
not修饰符
not 匹配器,可以将匹配后的结果进行取反。
toBeGreaterThan
toBeGreaterThan 匹配器,期望值是否大于匹配器的参数。
toBeLessThan
toBeLessThan 匹配器,期望值是否小于匹配器的参数。
toBeGreaterThanOrEqual
toBeGreaterThanOrEqual 匹配器,期望值是否大于或等于匹配器的参数。
toBeCloseTo
js 中,浮点数值在相加时不准确,使用 toBeCloseTo 匹配器解决,趋近于 0.3。
toMatch
toMatch 匹配器,匹配当前字符串中是否含有这个值,支持正则。
toContain
toContain 匹配器,判断当前数组中是否包含这个元素,Set 也可以使用。
toThrow
toThrow 匹配器,可以捕捉抛出的异常,参数为抛出的 error ,可以用来判断是否为某个异常。
以上就是 Jest 中比较基础的匹配器,可以结合 初始化 + 配置 + 基础匹配器 进行书写测试用例。
命令行操作
在运行 npm run test 命令的时候,控制台执行测试用例成功或失败后都会像下面的图片一样出现几行提示,让你按对应的键进行操作。
上面几个命令行的意思如下:
1. f 只会跑测试未通过的用例,再次点击 f 会取消当前模式。
我们使用一个失败的测试用例做一下示范。
按下 f 后,Jest 只会执行刚才失败的测试用例。
2. 只监听已改变的文件,如果存在多个测试文件,可以开启,会与当前 git 仓库中的提交进行比较,需要使用 git 来监听哪个文件修改了,也可以将 --watchAll 改为 --watch 只会运行修改的文件。
3. 根据测试用例文件的正则表达式,过滤需要执行的测试用例文件,No tests found, exiting with code 0 如果填写不对会进行提示,并不会跑任何测试用例。
4. 根据测试用例描述的正则表达式,过滤需要执行的测试用例。5. 退出测试用例监听。
异步测试
在正常的业务开发中,项目中不只有同步代码,还会有请求接口的异步代码,异步代码的测试与同步代码有稍许不同,我们来看一下。
编写一个接口请求
对异步请求进行测试
需要注意的是,如果传入了形参 done,但是没有使用,这个测试用例就会处于一直执行的状态,直到执行超时。
还可以结合 promise 进行使用
钩子函数
钩子函数可以当作一个测试用例的生命周期来看待,有 beforeAll 、beforeEach 、afterEach 、afterAll 。
以下是一些关于钩子函数的概念和场景:
beforeAll:在所有测试用例执行前运行
beforeEach:在每个测试用例执行前执行一次
afterEach:在每个测试用例执行后执行一次
afterAll:在所有测试用例结束后运行
有时候,需要测试一个类中的多个方法,这些方法可能会反复操作同一个对象上的属性。如果使用同一个实例,就会相互干扰,导致测试用例无法通过。此时,需要使用不同的实例来进行测试。
Counter 类
我们想要测试里面的 add 和 minus 方法是否正确,需要实例化一个对象进行测试。但是下面的测试用例使用的永远都是同一个实例,第二个测试用例永远都不会通过。因为执行了第一个测试用例,第二个测试用例的值只能是 0。
需要使用钩子函数,在每次执行测试用例的时候,都让他重新实例化一个对象
分组方法 discribe
加上 describe 方法的执行效果如下图:
Mock
在日常开发中,当前端开发差不多后,后端接口可能还没有提供,这个时候我们就要用 Mock 数据。而 Jest 也有 Mock 方法,用于模拟一些 JavaScript 的函数等。
我们先来一个比较简单的 mock.fn
Mock 高阶用法
如果需要通过修改请求的方式进行测试,而不使用测试框架,我们可能需要修改请求的代码逻辑。但是,Jest 提供了一种高级的 Mock 方法。我们只需在项目根目录下创建一个名为 __mocks__ 的文件夹,然后在其中自定义文件内容并导出,就可以使用自己定义的 Mock 函数而不必修改请求代码逻辑。
书写测试用例文件,引入 __mocks__ 文件夹中的函数
Mock-timers
在特定的业务中,需要使用到定时器,测试的时候也是需要修改代码来测试不同时间,最主要的一点是,我们需要等时间才能看到我们的执行结果,Jest 也有关于定时器的 Mock 函数。
snapshot 快照
到这里我们已经可以测试一些代码了,但是我们要如何捕捉执行结果和当前做对比呢?这时候就要使用快照功能了。
行内快照生成
对 dom 节点测试
Jest 内部自己模拟了一套 jsDom ,可以在 node 的环境下执行需要浏览器环境 dom 的测试用例。
VSCode 插件
Jest Snippets 用于快速生成 Jest 代码块的工具。
Jest 能够检测当前文件夹中的测试用例并自动运行测试,还支持可视化操作,更新、执行以及单个执行等功能,非常方便!
常用配置解读
小结
在实际业务应用中,我们建议对可复用的组件、工具函数、工具类等一些无副作用,可预知结果的代码来进行单元测试。在前期开发过程中的投入会大于没有单元测试的投入,因为要写一些测试用例,还要执行测试用例,优化代码等。但是在长久迭代中,这种方法会比没有进行单元测试的模块更加稳定。
代码地址
- 前置 demo :https://github.com/Jadony/Jest-demo
- Jest 简单配置:https://github.com/Jadony/jest-config
- Jest 匹配器:https://github.com/Jadony/jest-matchers
- 异步代码测试:https://github.com/Jadony/jest-async
- Jest 钩子函数:https://github.com/Jadony/jest-hook
- Jest 的 mock 函数:https://github.com/Jadony/jest-mock
- Jest 的快照:https://github.com/Jadony/jest-snapshot
- Jest 对 Dom 节点的测试:https://github.com/Jadony/jest-dom
参考文档
- 《前端要学的测试课 从Jest入门到TDD/BDD双实战》(https://coding.imooc.com/class/chapter/372.html#Anchor)