为什么要写单元测试
- 减少bug,避免低级错
- 提高项目代码质量
- 快速定位问题
- 减少调试时间,提交开发效率
- 便于重构
什么是Jest
Jest 中文网: Jest 是一个令人愉快的 JavaScript 测试框架,专注于 简洁明快.这些项目都在使用 Jest: Babel, TypeScript, Node, React, Angular, Vue 等!
Jest优点
Jest
安装配置简单,非常容易上手,几乎是零配置的,通过npm 命令安装就可以直接运行了Jest
自动集成了断言库,不需要再引入第三方的断言库Jest
内置有代码覆盖率报告功能,可以在整个项目范围里收集代码覆盖率信息,包括未经受测试的文件.Jest
的测试用例是并行执行的,它会自动识别一些测试文件,只执行发生改变的文件所对应的测试,提升了测试速度Jest
提供snapshot
功能,通过比对 UI 代码生成的快照文件,实现对vue
框架的自动测试jest
可以生成sonarQube需要的测试覆盖率报告,可将数据上传sonarvue
官方推荐的单元测试框架,对vue
框架友好,并且vue
脚手架可选自定集成的单元测试框架.
Jest安装与配置
安装插件
首先我们需要安装jest需要的一些插件
:
jest
: Jest@vue/test-utils
:Vue Test Utils 是Vue.js 官方的单元测试实用工具库babel-jest
:使用Babel自动编译JavaScript代码vue-jest
:使用vue-jest去编译.vue 文件jest-serializer-vue
:生成vue快照的序列化器的模块,进行snapshot tests会需要jest-transform-stub
:处理css|图片|字体的预处理器jest-sonar-reporter
(可选):Jest的自定义结果处理器.处理器将Jest的输出转换为Sonar的通用测试数据格式.
配置jest.config.js
module.exports = {
verbose: true,
bail: 1,
moduleFileExtensions: [
'vue',
'js',
'json'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'.+\\.(css|less|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
'^.+\\.js$': 'babel-jest',
'.*\\.vue$': 'vue-jest',
'^.+\\.svg$': 'jest-svg-sprite-loader'
},
snapshotSerializers: [ 'jest-serializer-vue' ],
testResultsProcessor: 'jest-sonar-reporter',
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,vue}',
],
coverageReporters: ['html', 'lcov', 'text-summary'],
coverageDirectory: './test/coverage',
coveragePathIgnorePatterns: [ '/node_modules/' ],
coverageThreshold: {
global: {
branches: 20,
functions: 20,
lines: 20,
statements: 20
}
},
testMatch: [
'**/*.spec.js'
],
};
verbose
: 多于一个测试文件运行时展示每个测试用例测试通过情况bail
: 参数指定只要有一个测试用例没有通过,就停止执行后面的测试用例moduleFileExtensions
:jest
需要检测测的文件类型moduleNameMapper
: 从正则表达式到模块名称的映射,和webpack的alisa类似transform
: 预处理器配置snapshotSerializers
:Jest
在快照测试中使用的快照序列化程序模块的路径列表testResultsProcessor
: 自定义结果处理器,用jest-sonar-reporter
输出sonar
需要的通用测试数据格式collectCoverage
: 是否进行覆盖率收集collectCoverageFrom
: 需要进行收集覆盖率的文件,会依次进行执行符合的文件coverageReporters
:Jest
在编写覆盖率报告的配置,添加"text
"或"text-summary
"在控制台输出中查看覆盖率摘要coverageDirectory
:Jest
输出覆盖信息文件的目录coveragePathIgnorePatterns
: 需要跳过覆盖率信息收集的文件目录coverageThreshold
: 覆盖结果的最低阈值设置,如果未达到阈值,jest
将返回失败testMatch
:Jest
用于检测测试的文件,可以用正则去匹配
Jest钩子函数
describe / test
describe
,test
代表一个执行块(作用域),可以通过describe
块来将测试分组,如果没有describe
,那整个文件就是一个describe.
执行顺序
Jest
会在执行具体的test
块之前执行所有的describe
处理器部分.
describe("unit test", ()=>{
it('10 is 10', ()=>{
expect(10).toBe(10)
})
test('A is A',()=>{
expect('A').toBe('A');
})
})
jest
可以支持beforeAll
,afterAll
,beforeEach
和afterEach
来编写初始化代码.- 默认情况下,
before
和after
的块可以应用到文件中的每个测试. 此外可以通过describe
块来将测试分组. - 当
before
和after
的块在describe
块内部时,则其只适用于该describe
块内的测试. - 执行顺序的时候,是先执行
describe
,然后在执行各个勾子函数的.
beforeAll(() => console.log('外 - beforeAll')) // 1
afterAll(() => console.log('外 - afterAll')) // 12
beforeEach(() => console.log('外 - beforeEach')) // 2 6
afterEach(() => console.log('外 - afterEach')) // 4 10
test('', () => console.log('外 - test')) // 3
describe('describe inside', () => {
beforeAll(() => console.log('内 - beforeAll')) // 5
afterAll(() => console.log('内 - afterAll')) // 11
beforeEach(() => console.log('内 - beforeEach')) // 7
afterEach(() => console.log('内 - afterEach')) // 9
test('', () => console.log('内 - test')) // 8
})
Jest snapshot test
jest
可以对整体的html
进行测试有没有更改,方法就是Snapshot
测试(快照测试),目前来说是jest
专有的测试方法,通过对比前后的快照,可以很快找出UI
的变化之处.
wrapper.html()
获取整个DOM
toMatchSnapshot()
第一次运行快照测试时会生成一个快照文件,之后每次执行测试的时候,会生成一个快照,然后对比最初生成的快照文件,如果没有发生改变,则通过测试;否则测试不通过,同时会输出结果,对比不匹配的地方.- 代码发生变化的时候,通过 控制台 按 u 来更新 快照
- 多处
test case
更改的时候,按 i 来交互式的 更新快照
import { shallowMount } from '@vue/test-utils'
import Demo from '@/com/Demo.vue';
it('template内DOM结构的snapshot测试', () => {
const wrapper = shallowMount(Demo)
expect(wrapper.html()).toMatchSnapshot()
wrapper.destroy()
})
Jest Mock
Jest
中的三个与 Mock
函数相关的API
,分别是jest.fn()
,jest.spyOn()
,jest.mock()
jest.fn()
jest.fn()
是创建Mock
函数最简单的方式,如果没有定义函数内部的实现,jest.fn()
会返回undefined
作为返回值jest.fn()
所创建的Mock
函数还可以设置返回值,定义内部实现或返回Promise
对象
import { ajaxMock } from '../../__mock__/ajax.mock';
describe('ajax_test', () => {
it('ajax_是否被调用', async () => {
const mockFun1 = jest.fn();
mockFun1.mockReturnValueOnce(456).mockReturnValueOnce(789);
const mockFun2 = jest.fn(() => { return 456; });
await ajaxMock(mockFun1);
await ajaxMock(mockFun1);
await ajaxMock(mockFun2);
expect(mockFun1).toBeCalled(); // 被执行
expect(mockFun1.mock.calls.length).toBe(2); // 调用次数
expect(mockFun2.mock.results[0].value).toBe(456); // 返回结果
});
});
jest.mock()
实际开发过程,一些请求方法可能我们在其他模块被调用的时候,并不需要进行实际的请求,可以使用jest.mock()
去mock
整个模块
import call from '../src/call';
import ajax from '../src/ajax';
jest.mock('../src/ajax.js');
test('mock 整个 ajax.js模块', async () => {
await call.getData();
expect(ajax.ajaxGetData).toHaveBeenCalled();
expect(ajax.ajaxGetData).toHaveBeenCalledTimes(1);
})
jest.spyOn()
jest.spyOn()
方法同样创建一个mock
函数,但是该mock
函数不仅能够捕获函数的调用情况,还可以正常的执行被spy
的函数.实际上,jest.spyOn()
是jest.fn()
的语法糖,它创建了一个和被spy
的函数具有相同内部代码的mock
函数.
import call from '../src/call';
import ajax from '../src/ajax';
test('使用jest.spyOn()监控ajax.ajaxGetData被正常调用', async() => {
expect.assertions(2);
const spyFn = jest.spyOn(ajax, 'ajaxGetData');
await call.getData();
expect(spyFn).toHaveBeenCalled();
expect(spyFn).toHaveBeenCalledTimes(1);
})
Jest报告上传sonar
jest.config.js
添加配置
testResultsProcessor: 'jest-sonar-reporter'
sonar-project.properties
的运行命令中添加
// xml的位置
sonar.testExecutionReportPaths=test/covrage/test-report.xml
// lcov.info的位置
sonar.javascript.lcov.reportPaths=test/covrage/lcov.info
Jest coverage
jest
执行完会生成一个覆盖率统计表, 所有在覆盖率统计文件夹下的文件都会被检测,覆盖率指标:
Statements
: 语句覆盖率,执行到每个语句;Branches
: 分支覆盖率,执行到每个if代码块;Functions
: 函数覆盖率,调用到程式中的每一个函数;Lines
: 行覆盖率, 执行到程序中的每一行
Jest常用的断言
toBe()
----测试具体的值toEqual()
----测试对象类型的值toBeCalled()
----测试函数被调用toHaveBeenCalledTimes()
----测试函数被调用的次数toHaveBeenCalledWith()
----测试函数被调用时的参数toBeNull()
----结果是null
toBeUndefined()
----结果是undefined
toBeDefined()
----结果是defined
toBeTruthy()
----结果是true
toBeFalsy()
----结果是false
toContain()
----数组匹配,检查是否包含toMatch()
----匹配字符型规则,支持正则toBeCloseTo()
----浮点数toThrow()
----支持字符串,浮点数,变量toMatchSnapshot()
----jest
特有的快照测试.not.toBe()
----前面加上.not
就是否定形式
参考资料:
关注UU
关注公众号【前端UU】
,定期获取好文推荐哟~