单元测试主要包含断言、测试框架、测试用例、测试覆盖率、mock、持续集成等几个方面,由于Node的特殊性,它还会加入异步代码测试和私有方法的测试这两个部分。
断言
在程序设计中,断言是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示程序开发者预期的结果——当程序运行到断言的位置时,对应的断言应该为真。若断言不为真,程序会中止运行,并出现错误信息。
断言用于检查程序在运行时是否满足期望。
node自带断言就是assert模块,具体使用请查看官方文档:地址
const assert = require('assert');
assert.equal(Math.max(1, 100), 100);
常用的第三方断言库有chai,官方地址:链接
测试框架
前面提到断言一旦检查失败,将会抛出异常停止整个应用,这对于做大规模断言检查时并不友好。更通用的做法是,记录下抛出的异常并继续执行,最后生成测试报告。这些任务的承担者就是测试框架。
常用的第三方测试框架有mocha,jest,他们之间的区别在于jest内容更加全面,集成了断言,jsdom模拟浏览器DOM环境。
测试用例
每个功能至少要包含一个测试用例,今天介绍jest写测试用例,先安装jest和@types/jest,后面这个是类型声明文件,可以帮助我们在写代码的时候会有语法提示。
npm i jest @types/jest
默认会测试spec和test结尾的js文件,所有写单元测试的文件名必须是sum.spec.js或者sum.test.js这个的格式。
然后需要在package.json里面增加一段脚本
{
"scripts": {
"test": "jest"
}
}
通过运行npm run test
最后会生成测试报告
在测试用例中使用it
语法表示一个用例,expect
表示预期结果,toBe
表示三个等号的比较,如果条件成立单元测试即可通过。
匹配器
相当,不相等,包含,等等,匹配的关系
// describe表示分组,分组里面一个个的用例
describe('测试基本方法', () => {
it('测试sum函数', () => {
expect(sum(1, 2)).toBe(3)
})
it('测试1+1=2', () => {
expect(1 + 1).toBe(2)
})
it ('对象比较', () => {
expect({name: 1}).toEqual({name: 1})
})
})
it('测试不相等', () => {
expect(1+1).not.toBe(3) // 1+1不等3
expect(3).toBeLessThan(5) // 3<5
})
it('测试包含', () => {
expect('hello').toContain('h') // 参数是字符串
expect('hello').toMatch(/h/) // 参数可以是正则
})
测试DOM
it('测试删除DOM', () => {
document.body.innerHTML = `<div><button></button></div>`
let button = document.querySelector('button')
expect(button).not.toBe(null)
// 自己写的移除的DOM方法
removeNode(button);
button = document.querySelector('button')
expect(button).toBe(null)
})
测试异步
// 回调
export const getDataCallback = fn => {
setTimeout(() => {
fn({name: 'callback'})
}, 1000);
}
// promise
export const getDataPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({name: 'promise'})
}, 1000);
})
}
import { getDataCallback, getDataPromise } from '../util'
// 异步回调
it('测试回调函数', (done) => {
// done参数
getDataCallback((data) => {
expect(data).toEqual({ name: 'callback' })
done() // 标识调用完成
})
})
it('测试promise', () => {
// 返回的promise会等待完成
return getDataPromise().then(data => {
expect(data).toEqual({ name: 'promise' })
})
})
it('测试promise', async () => {
// 使用await语法
const data = await getDataPromise()
expect(data).toEqual({ name: 'promise' })
})
mock
如果我们要测试接口,比如使用axios发送api请求
import axios from 'axios'
// 测试接口调用
export const fetchUser = () => {
return axios.get('/list')
}
我们并不会真实的发送API请求,而是模拟数据返回,这个时候可以在__mocks__
下面建axios.js
文件,jest就不会再使用node_modules文件中的axios发请求了,而是使用我们自己写的axios模块。
// mock axios
export default {
get(url) {
return new Promise((resolve, reject) => {
// 根据请求路径mock数据
if (url === '/list') {
resolve({ name: 'hello' })
}
})
}
}
import { fetchUser } from '../api'
it('测试请求列表', async () => {
// 把请求的逻辑mock
const data = await fetchUser()
expect(data).toEqual({name: 'hello'})
})
测试覆盖率
想要查看测试覆盖率,我们需要先生成配置文件,通过命令
npx jest --init
然后还需要在package.json中添加一段脚本
{
"scripts": {
"test": "jest --coverage"
}
}
通过命令运行npm run test
最后会生成测试覆盖率
测试风格
我们将测试用例的不同组织方式称为测试风格,现今流行的单元测试风格主要有TDD(测试驱动开发)和BDD(行为驱动开发)两种。
关注点不同。
- TDD关注所有功能是否被正确实现,每一个功能都具备对应的测试用例;一般为先写好测试用例,然后再开发代码,这样就可以让测试用例覆盖所有代码。
- BDD关注整体行为是否符合预期,适合自顶向下的设计方式。一般是先开发代码,再写单元测试。这个较为常用。