自动化测试
让软件测试代替人工测试,提高代码质量。
好处:
- 快速定位 bug
- 有测试的代码,质量比较高
- 提高代码可维护性
什么样的代码需要测试?开发类库、组件库、框架等。
分类
- 黑盒测试(功能测试)、白盒测试(代码的具体逻辑)
- 单元测试(以最小的单元实现测试,如方法、组件、模块等)、集成测试(组合在一起进行测试)
- TDD(测试驱动开发,先写测试再写逻辑)、BDD(行为驱动开发,根据用户行为进行测试)
常见的测试框架
- Karma 把测试跑在真正的浏览器上,可以测试 UI 组件
- mocha 提供了一个测试环境,断言库 chai,sinon 模拟一些假的方法
- jest 基于 jsdom,用 js 对象模拟浏览器环境,缺点不能测试样式,0 配置
第一个测试用例
// ./src/1.parser.js
const parser = (str) =>{
const obj = {};
str.replace(/([^&=]+)=([^&=]+)/g,function () {
obj[arguments[1]] = arguments[2]
})
return obj;
}
const stringify = (obj) =>{
const arr = [];
for(let key in obj){
arr.push(`${key}=${obj[key]}`);
}
return arr.join('&')
}
// export 导出的是接口 供别人使用的接口 要通过解构的方式去获取
// export default 导出具体的值
export {
parser,
stringify
}
// ./1.parser.test.js
// 测试文件默认一般以.test.js 结尾 .spec.js 结尾
import {
parser,
stringify
} from './src/1.parser'
// describe 套件 =》 it 一堆用例
// 默认jest只支持 node语法 babel babel-jest
describe('测试parser', () => {
it('测试parser是否能正常解析', () => {
expect(parser('a=1&b=2&c=3')).toEqual({
a: "1",
b: "2",
c: "3"
})
})
})
describe('测试stringify', () => {
it('测试stringify是否能正常解析', () => {
expect(stringify({a:1,b:2})).toEqual("a=1&b=2")
})
})
- 安装 jest,
npm i -D jest - 安装 babel,
npm i -D @babel/presets-env,配置文件.babelrc
{
"presets": [
["@babel/preset-env",{
"targets":{"node":"current"}
}]
]
}
常用匹配器
it('测试相等情况 (全等) (长得一样) 是不是真的 是不是假的',()=>{
expect(1+1).toBe(2); // ===
expect({name:'zf'}).toEqual({name:'zf'}); // 比较长得是否一致 toEqual
expect(true).toBeTruthy();
expect(false).toBeFalsy();
})
it('测试不相等 (大于 小于 大于等于 小于等于)',()=>{
expect(1+1).not.toBe(3);
expect(1+1).toBeLessThan(3);
expect(1+1).toBeGreaterThanOrEqual(0);
})
it.only('是否包含 是否匹配',()=>{ // it.only只测试当前文件的这个用例
expect('hello').toContain('o');
expect('hello').toMatch(/hello/)
})
// npx jest --watchAll
// › Press f to run only failed tests.
// › Press o to only run tests related to changed files. 如果你有git 每次提交后
// › Press p to filter by a filename regex pattern.
// › Press t to filter by a test name regex pattern.
// › Press q to quit watch mode.
// › Press Enter to trigger a test run.
jsdom 操作
// ./src/2.dom.js
const addNode = (node,parent)=>{
parent.appendChild(node)
}
const removeNode = (node) =>{
node.parentNode.remove(node);
}
export {
addNode,
removeNode
}
// ./2.dom.test.js
import {addNode,removeNode} from './src/2.dom';
it('测试能否正常添加节点',()=>{
// jsdom 假的dom
document.body.innerHTML = '<div id="wrapper"></div>';
let button = document.createElement('button');
let wrapper = document.querySelector('#wrapper');
addNode(button,wrapper);
wrapper = document.querySelector('#wrapper');
let btn =wrapper.querySelector('button');
expect(btn).not.toBeNull()
});
// 不停的去测试对应的逻辑,单元测试就是测试某个方法是否能达到我的预期效果
it('测试是否能够正常删除',()=>{
document.body.innerHTML = '<div id="wrapper"><button id="button"></button></div>';
let btn = document.querySelector('#button');
expect(btn).not.toBeNull()
removeNode(btn);
btn = document.querySelector('#button');
expect(btn).toBeNull()
})
异步测试
默认不等待异步后再测试。
- 使用 done
- 使用 jest.useFakeTimers()替换掉定时器
// ./src/3.async.js
const getDataByCallback = (cb) => {
setTimeout(() => {
cb({
name: 'jw'
})
}, 1000);
}
const getDataByPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: 'jw'
})
}, 1000);
})
}
export {
getDataByCallback,
getDataByPromise
}
// ./3.async.test.js
// --watchAll = 监控全部 w
// --watch = o 只监控变化的文件
import {
getDataByCallback,
getDataByPromise
} from './src/3.async'
// 1.异步的回调方式可以传入done 函数 什么时候完成什么时候调用
it('测试回调函数 获取数据', (done) => {
//jest.useFakeTimers(); // 使用假的定时器
getDataByCallback((data) => {
expect(data).toEqual({
name: 'jw'
});
done()
})
// jest.runAllTimers(); // 运行所有的定时器
// jest.runOnlyPendingTimers(); // 只运行当前等待队列的一个
// jest.advanceTimersByTime(10000)
})
// 如果是promise done 、 async+await
it('测试promise获取数据', async () => {
let data = await getDataByPromise()
expect(data).toEqual({
name: 'jw'
});
});
mock 函数
// ./src/4.fn.js
export const map = (arr,fn) =>{
for(let i = 0 ; i< arr.length;i++){
fn(arr[i],i);
}
}
// ./4.fn.js
import {map} from './src/4.fn';
it('测试map方法',()=>{
// jest.fn 模拟一个方法,让用户调用,用户调用这个函数的所有信息 都会被记录到 当前的mock属性上
let fn = jest.fn(); // 模拟函数 可以记录被执行的过程
map([1,2,3],fn);
// expect(fn.mock.calls.length).toBe(3);
expect(fn.mock.calls[0][0]).toBe(1)
expect(fn.mock.calls[0][1]).toBe(0)
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(3);
})
覆盖率
生成 jest 配置文件,npx jest —init
执行时npm run jest —converage
mock 接口
- 在文件的当前目录建立名为mocks的文件夹,在里面新建同名文件,编写返回方法,适用于整个文件是都是接口的情况,如有真实方法,可用 jest.requireActual 做替换;
- 只 mock 某个方法,mock 掉某个包
// ./src/6.ajax.js
import axios from 'axios';
export const fetchData = ()=>{
return axios.get('/user'); // 获取用户数据 // ['张三','李四]
}
export const sum = (a,b)=>{
return a+b
}
// ./6.ajax.test.js
// jest.mock('./src/6.ajax'); // 替换整个文件
import {fetchData,sum} from './src/6.ajax';
// let {sum} = jest.requireActual('./src/6.ajax')
it('测试能否正常获取用户数据',async ()=>{
let r = await fetchData();
expect(r).toEqual(['张三','李四'])
})
it('测试求和函数', ()=>{
expect(sum(1,1)).toBe(2)
})
// ./src/__mocks__/6.ajax.js
export const fetchData = () => {
return new Promise((resolve,reject)=>resolve(['张三','李四']))
};
// ./src/__mocks__/ajax.js
export default {
get(url) {
if (url == '/user') {
return new Promise((resolve) => resolve(['张三', '李四']))
}
}
}