前端单元测试
一:什么是单元测试
1、概念:
单元测试(unit testing)是指对软件中的最小可测试单元进行检查和验证1。它是一种软件测试方法,用于确保每个独立的软件模块(某个函数或者某个组件)都按照预期运行。
2、单元测试与开发人员有什么关系
其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本。
3、单元测试的好处
对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。
4、怎么单元测试
进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。我认为,比较简单的办法是为产品工程建立对应的测试工程,为每个类建立对应的测试类,为每个函数(很简单的除外)建立测试函数。我们前端选择jest库来写单元测试函数。
二:为什么选择jest库
Jest是Facebook出品的一个测试框架,相对于其他测试框架,它的一大特点就是内置了常用的测试工具,比如自带断言、Mock功能、测试覆盖率工具,实现了开箱即用1。此外,Jest也以其速度快、使用简单和容易配置而受欢迎2。
三:从0开始使用jest库
1、安装
npm config list
npm config set registry https://registry.npm.taobao.org //为npm加速
npm install jest -D
or
yarn add --dev jest
2、如何写测试文件
测试文件目录:tests
或者__tests__
测试脚本文件取名:utils.test.js
待测试文件: utils.js
3、jest库的一些简单知识
-
test方法:Jest封装的测试方法,一般填写两个参数,描述和测试方法
it是test方法的 it('测试函数1', () => { expect(sum(2, 3)).toBe(5) })
-
expect方法 :预期方法,就是你调用了什么方法,传递了什么参数,得到的预期是什么
expect(sum(2, 3)).toBe(5)
-
describe创建一个测试集。
这个方法接受两个参数,它的语法和test 的一致,第一个参数也是字符串,对这一组测试进行描述, 第二个参数是一个函数,函数体就是一个个的test 测试。 describe('一组测试函数', () => { it('测试函数1', () => { expect(sum(2, 3)).toBe(5) }) it('测试函数2', () => { expect(sum(2, 3)).toBe(4) }) })
-
匹配器–断言函数
toBe():绝对相等(===)
toEqual():简单类型绝对匹配;复杂类型内容结果的匹配
toBeNull():匹配null
toBeUndefined():匹配undefined
toBeDefined():匹配非undefined
toBeTruthy():匹配转化后为true
toBeFalsy():匹配转化后为false
toBeGreaterThan():相当于大于号
toBeLessThan():相当于小于号
toBeGreaterThanOrEqual():相当于大于等于号
toBeLessThanOrEqual():相当于大于等于号
toBeCloseTo():解决js浮点错误
toMatch(regExp/string):用正则表达式或者字符串匹配字符串片段
toContain():匹配数组或者Set中的某一项
toThrow():匹配异常处理,如果抛出了异常就过测试用例
expect({a:1}).toBe({a:1})//判断两个对象是否相等
expect(1).not.toBe(2)//判断不等
expect(n).toBeNull(); //判断是否为null
expect(n).toBeUndefined(); //判断是否为undefined
expect(n).toBeDefined(); //判断结果与toBeUndefined相反
expect(n).toBeTruthy(); //判断结果为true
expect(n).toBeFalsy(); //判断结果为false
expect(value).toBeGreaterThan(3); //大于3
expect(value).toBeGreaterThanOrEqual(3.5); //大于等于3.5
expect(value).toBeLessThan(5); //小于5
expect(value).toBeLessThanOrEqual(4.5); //小于等于4.5
expect(value).toBeCloseTo(0.3); // 浮点数判断相等
expect('Christoph').toMatch(/stop/); //正则表达式判断
expect(['one','two']).toContain('one'); //匹配数组
test('compiling android goes as expected', () => {
expect(compileAndroidCode).toThrow();
expect(compileAndroidCode).toThrow(ConfigError); //判断抛出异常
})
4、运行
将如下代码添加到 package.json
中:
{
"scripts": {
"test": "jest"
}
}
运行环境配置
要解决这个问题,需要用到 Babel 把代码进行转化就OK了,如果想让 Babel 支持 ESM,我们需要三个包:
@babel/core:babel核心库
@babel/preset-env:进行 ES 语法转换的库
babel-jest:和 Jest 通信的库,用来检测是否安装了上面两个依赖
yarn add -D @babel/core @babel/preset-env babel-jest 或者
npm i @babel/core @babel/preset-env babel-jest -D
//.babelrc 配置:主要有两个参数 presets(预设) plugins(插件)
// presets :是某一类 plugin 的集合,包含了某一类插件的所有功能。
// plugin : 将某一种需要转化的代码,转为浏览器可以执行代码。
添加.babelrc文件进行简单的babel配置
{
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
}
最后,运行 yarn test
或者 npm test
,Jest 将输出如下信息
5、jest覆盖率配置
最简单
"scripts": {
"test": "jest --coverage"
},
出测试覆盖率报告
npx jest --coverage
6、 异步测试
基于jest提供的两个方法jest.useFakeTimers
和jest.runAllTimers
可以更优雅的对延时功能的测试。
describe('定时器相关测试', () => {
// 开启定时函数模拟
jest.useFakeTimers();
function foo(callback) {
console.log('foo...')
setTimeout(() => {
callback && callback();
}, 1000)
}
it('断言异步测试', () => {
//创建mock函数,用于断言函数被执行或是执行次数的判断
const callback = jest.fn();
foo(callback);
expect(callback).not.toBeCalled();
//快进,使所有定时器回调
jest.runAllTimers();
expect(callback).toBeCalled();
})
});
还可以使用async await
describe('定时器相关测试', () => {
function fetchData(v){
return new Promise((resolve,reject) => {
resolve(v)
})
}
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
}
7、Dom测试
实现dom渲染测试,以及点击事件等交互功能测试。
describe('Dom测试', () => {
it('测试按钮是否被渲染 ', () => {
document.body.innerHTML = `
<div>
<button id='btn'>小按钮</button>
</div> `
console.log(document.getElementById('btn'), document.getElementById('btn').toString())
expect(document.getElementById('btn')).not.toBeNull();
expect(document.getElementById('btn').toString()).toBe("[object HTMLButtonElement]");
});
it('测试点击事件', () => {
const onclick = jest.fn();
document.body.innerHTML = `
<div>
<button id='btn'>小按钮</button>
</div> `
const btn = document.getElementById('btn');
expect(onclick).not.toBeCalled();
btn.onclick = onclick;
btn.click();
expect(onclick).toBeCalled();
expect(onclick).toHaveBeenCalledTimes(1);
btn.click();
btn.click();
expect(onclick).toHaveBeenCalledTimes(3);
});
});