官方文档:https://jestjs.io/zh-Hans
一、什么是Jest
Jest 是 Facebook 开发的 Javascript 测试框架,用于创建、运行和编写测试的 JavaScript 库。Jest 作为 NPM 包发布,可以安装并运行在任何 JavaScript 项目中。Jest 是目前前端最流行的测试库之一。
与Mocha框架的对比。
框架断言异步代码覆盖率
Mocha不支持(需要其他库支持)友好不支持(需要其他库支持)
Jest默认支持友好支持
1. Mocha 生态好,但是需要较多的配置来实现高扩展性
2. Jest 开箱即用
无论是受欢迎度和写法上,Jest 都有很大的优势,因此推荐你使用开箱即用的 Jest
[if !supportLists]二、[endif]环境配置
1.安装JEST
npm install --save-dev jest
2.生产基础配置文件
npm init jest@latest
3.使用Babel
原因:nodejs 采用的是 CommonJS 的模块化规范,使用 require 引入模块;而 import 是 ES6 的模块化规范关键字。想要使用 import,必须引入 babel 转义支持,通过 babel 进行编译,使其变成 node 的模块化代码
解决:为了能使用这些新特性,我们就需要使用babel 把 ES6 转成 ES5 语法
// 安装依赖
npm install --save-dev babel-jest @babel/core @babel/preset-env
可以在工程的根目录下创建一个babel.config.js文件用于配置与你当前Node版本兼容的Babel:
babel.config.js
module.exports = { presets: [['@babel/preset-env', {targets: {node: 'current'}}]],};
通过ts-jest实现,ts-jest 是一个支持 sourcemap 的 TypeScript 预处理器,让你使用 TypeScript 编写 Jest 测试项目
npm install --save-dev ts-jest
(1)jest.config.js
module.exports = {
'transform': {
'^.+\\.jsx?$': 'babel-jest', // 这个是jest的默认配置
'^.+\\.ts?$': 'ts-jest', // typescript转换
},
globals: {
'ts-jest': {
diagnostics: false
}
},
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'^@/(.*)$': '<rootDir>/src/$1',
'^@customer/(.*)$': '<rootDir>/src/customer/uni/$1',
'^@components/(.*)$': '<rootDir>/src/pages/components/$1',
'^@pages/(.*)$': '<rootDir>/src/pages/$1',
},
// '^@scss/(.*)$': '<rootDir>/src/$1',
testEnvironment: 'jsdom',
// 'transformIgnorePatterns': [
// 'node_modules/(?!(react-native|my-project|react-native-button)/)'
// ]
testPathIgnorePatterns: ['/node_modules/'],
setupFiles: ['<rootDir>/tests/unit/setup.js'],
collectCoverage: true,
coverageDirectory: 'tests/unit/coverage',
collectCoverageFrom: [
'src/pages/smart/uni/launcher/**/*.{js,jsx,ts,tsx}', // 只收集 src/pages/uni 目录下的 JS 和 TS 文件的覆盖率
'!**/node_modules/**', // 排除 node_modules 目录
'!**/sysDefImg/**' // 排除 sysDefImg 目录
],
resetMocks: true,
}
(2)babel.config.js
module.exports = {
'presets': [
[
'@babel/preset-env',
{
'targets': {
'node': true
}
}
]
]
}
(3)package.json
"scripts": {
"jest": "jest --watch"
},
这样环境配置就完成了~~~可以着手写测试用例了
6.举个例子
我们先写一个两数相加的函数。
首先,创建sum.js 文件︰
function sum(a, b) { return a + b;}module.exports = sum;
然后,创建名为sum.test.js 的文件。 此文件中将包含我们的实际测试︰
const sum = require('./sum');test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3);});
随后,将下列配置内容添加到您的package.json:
{ "scripts": { "test": "jest" }}
最后,运行yarn test 或 npm run test ,Jest将打印下面这个消息:
PASS ./sum.test.js✓ adds 1 + 2 to equal 3 (5ms)
您刚刚完成了您的首个Jest 测试!
三、相关参数
Jest会将这些方法和对象注入到测试文件的全局环境里, 所以你在使用的时候不再需要进行require或者import。 如果你习惯编写明确的导入,你可以在测试文件顶部添加 import {describe, expect, test} from '@jest/globals'。
import {expect, jest, test} from '@jest/globals';
describe(name, fn) 是一个将多个相关的测试组合在一起的块。 比如,现在有一个myBeverage对象,描述了某种饮料好喝但是不酸,通过以下方式测试:
const myBeverage = {delicious: true,sour: false,
};
describe('my beverage', () => { test('is not sour', () => { expect(myBeverage.sour).toBeFalsy(); });
});
注意:这不是强制的,你甚至可以直接把 test 块直接写在最外层。 但是如果你习惯按组编写测试,使用 describe 包裹相关测试用例更加友好。
2.test(name, fn, timeout)
别名:it(name, fn, timeout)
第一个参数是测试名称; 第二个参数是包含测试期望的函数。 可选地传入第三个参数 timeout(毫秒) 指定测试超时时间。 The default timeout is 5 seconds.
当测试用例test过多时,可以进行测试分组,写到多个describe进行分组管理
更多参数详情参考官方文档:https://jestjs.io/zh-Hans/docs/api
3.常用的方法
(1).shallowMount和mount
---mount: 创建一个包含被挂载和渲染的 Vue 组件的 wrapper,它仅仅挂载当前实例
---shallowMount:和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,只挂载一个组件而不渲染其子组件 (即保留它们的存根),这个方法可以保证你关心的组件在渲染时没有同时将其子组件渲染,避免了子组件可能带来的副作用(比如Http请求等)
shallowMount和mount的区别:在文档中描述为"不同的是被存根的子组件",大白话就是shallowMount不会加载子组件,不会被子组件的行为属性影响该组件。
为什么使用shallowMount而不使用mount?
---我认为单元测试的重点在"单元"二字,而不是"测试",想测试子组件再为子组件写对应的测试代码即可(2).Wrapper:常见的有一下几种方法
Wrapper:Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。
Wrapper.vm:这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。
Wrapper.classes: 返回是否拥有该class的dom或者类名数组。
Wrapper.find:返回第一个满足条件的dom。
Wrapper.findAll:返回所有满足条件的dom。
Wrapper.html:返回html字符串。
Wrapper.text:返回内容字符串。
Wrapper.setData:设置该组件的初始data数据。
Wrapper.setProps:设置该组件的初始props数据。 (这是使用了,但没有效果)
Wrapper.trigger:用来触发事件。
原文链接:https://blog.csdn.net/qq_48959471/article/details/124620454
五、Expect断言
判断一个值是否满足条件,你会使用到expect函数。但你很少会单独调用expect函数,因为你通常会结合expect 和匹配器函数来断言某个值。六、匹配器的使用
最简单测试一个值的方法是使用精确匹配的方法。
test('two plus two is four', () => { expect(2 + 2).toBe(4);});
在上面的代码中,expect (2 + 2) 返回了一个"预期"的对象。 你通常不会对这些期望对象调用过多的匹配器。 在此代码中,.toBe(4) 是匹配器。 当 Jest 运行时,它会跟踪所有失败的匹配器,以便它可以为你打印出很好的错误消息。
toBe使用 Object.is来进行精准匹配的测试。 If you want to check the value of an object, use toEqual:
test('对象赋值', () => { const data = {one: 1}; data['two'] = 2; expect(data).toEqual({one: 1, two: 2});});
toEqual 递归检查对象或数组的每个字段。
提示:
toEqualundefined忽略具有属性、undefined数组项、数组稀疏性或对象类型不匹配的对象键。考虑到这些,请toStrictEqual改为使用。
您还可以使用与匹配相反的not 来进行测试:
test('adding positive numbers is not zero', () => { for (let a = 1; a < 10; a++) { for (let b = 1; b < 10; b++) { expect(a + b).not.toBe(0); } }});
代码中的undefined, null, and false有不同含义,若你在测试时不想区分他们,可以用真值判断。 Jest提供helpers供你使用。
toBeNull 只匹配 null
toBeUndefined只匹配undefined
toBeDefined 与 toBeUndefined 相反
toBeTruthy 匹配任何 if 语句为真
toBeFalsy 匹配任何 if 语句为假
例如:
test('null', () => { const n = null; expect(n).toBeNull(); expect(n).toBeDefined(); expect(n).not.toBeUndefined(); expect(n).not.toBeTruthy(); expect(n).toBeFalsy();});test('zero', () => { const z = 0; expect(z).not.toBeNull(); expect(z).toBeDefined(); expect(z).not.toBeUndefined(); expect(z).not.toBeTruthy(); expect(z).toBeFalsy();});
你应该使用最精确匹配的匹配器来测试你期望你的代码能干什么。
大多数的比较数字有等价的匹配器。
test('two plus two', () => { const value = 2 + 2; expect(value).toBeGreaterThan(3); expect(value).toBeGreaterThanOrEqual(3.5); expect(value).toBeLessThan(5); expect(value).toBeLessThanOrEqual(4.5); // toBe and toEqual are equivalent for numbers expect(value).toBe(4); expect(value).toEqual(4);});
对于比较浮点数相等,使用toBeCloseTo 而不是 toEqual,因为你不希望测试取决于一个小小的舍入误差。
test('两个浮点数字相加', () => { const value = 0.1 + 0.2; //expect(value).toBe(0.3);这句会报错,因为浮点数有舍入误差 expect(value).toBeCloseTo(0.3); //这句可以运行});});
您可以检查对具有toMatch 正则表达式的字符串︰
test('there is no I in team', () => { expect('team').not.toMatch(/I/);});test('but there is a "stop" in Christoph', () => { expect('Christoph').toMatch(/stop/);});
你可以通过toContain来检查一个数组或可迭代对象是否包含某个特定项:
const shoppingList = [ 'diapers', 'kleenex', 'trash bags', 'paper towels', 'milk',];test('shoppingList数组中包含milk', () => { expect(shoppingList).toContain('milk'); expect(new Set(shoppingList)).toContain('milk');});
七、钩子函数的使用
钩子函数在父级分组可作用域子集,类似继承
钩子函数同级分组作用域互不干扰,各起作用
先执行外部的钩子函数,再执行内部的钩子函数
1、beforeAll(fn, timeout)
文件内所有测试开始前执行的钩子函数。
使用beforeAll 会非常方便你设置一些在测试用例之间共享的全局状态。
2、afterAll(fn, timeout)
文件内所有测试完成后执行的钩子函数。如果 afterAll 定义在 describe 块的内部,它将会在 describe 块结束时执行
文件内每个测试开始前执行的钩子函数。使用beforeEach 非常方便你重新设置一些全局状态在每个测试开始前
文件内每个测试完成后执行的钩子函数。
使用afterEach 会非常方便你清理一些在每个测试中创建的临时状态。
八、测试覆盖率
运行命令:npx jest --coverage
在软件测试中,代码覆盖率是一个重要的指标,用于衡量测试用例集对被测代码的覆盖程度。常见的代码覆盖率包括语句覆盖、分支覆盖、条件覆盖、路径覆盖等。
代码覆盖率并不是越高越好,需要根据项目的实际情况,例如开发成本、项目进度、质量要求等进行平衡。本文通过一个示例项目了解如何使用Jest编写单元测试,理解测试覆盖率报告中的数据含义,以及提升单元覆盖率的方法。
项目准备
首先,Jest 需要一个包作为环境,进行单元测试。
mkdir lab-test-coverage
pnpm init
注意:这里的项目目录名称不推荐用coverage,因为这个名字会与生成的测试报告重名。
接着,编写项目代码与单元测试:
// index.js
module.exports = function(a, b) {
if (a && b) {
return 1;
} else {
return 2;
}
}
function second() {
// The following line is written
// to show stmts and lines coverage differences
let v1 = 1, v2 = 2, v3 = 3;
return "";
}
针对index 的单元测试:
// __tests__/index.spec.js
const main = require('../index');
describe('Test coverage demo', () => {
it('Test main', () => {
main(true, true);
});
it('Test main', () => {
main(true, false);
});
});
运行单元测试:
有了前面项目的基础,我们可以直接运行下面的命令来查看代码覆盖率:
jest --coverage
PASS __tests__/index.spec.js
Test coverage demo
✓ Test main
✓ Test main
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 50 | 100 | 50 | 66.66 |
index.js | 50 | 100 | 50 | 66.66 | 12-13
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.257 s, estimated 1 s
Ran all test suites.
可以看到,这里有 Stmts、Branch、Funcs、 Lines四个百分比。
[if !supportLists]· [endif]Stmts 语句覆盖率 Branch分支覆盖率 Funcs 函数覆盖率 Lines 行覆盖率
这是命令行中输出的一个精简版的报告。
与此同时,可以看到在项目的根目录下生成了一个coverage 目录,内容如下:
coverage
├── clover.xml
├── coverage-final.json
├── lcov-report
│ ├── base.css
│ ├── block-navigation.js
│ ├── favicon.png
│ ├── index.html
│ ├── index.js.html
│ ├── prettify.css
│ ├── prettify.js
│ ├── sort-arrow-sprite.png
│ └── sorter.js
└── lcov.info
在浏览器中打开index.html,可以看到更详细的报告。