一. 为之前的文件模块todo list项目做测试
- 先安装测试依赖jest
yarn add --dev jest
- 根据文档创建文件
sum.js
和sum.test.js
复制代码
js
// 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中添加脚本,之后运行
yarn test
js
"scripts": {
"test": "jest"
},
- 如果能得到以下图片的运行结果,则说明测试用例被正确执行了
- 如果错了为怎么样见下图
二.正式开始为项目做测试
- 主要测试db.js中的read和write
- 添加
__tests__
文件夹,这是jest的约定 - 在此文件夹下创建
db.spec.js
,这样命名是单元测试不成文的规定 - 先写一个最基本的测试用例,期待db.read是一个函数
js
const db = require('../db.js')
describe('db', fn => {
it('can read', ()=>{
expect(db.read instanceof Function).toBe(true)
})
it('can write', ()=>{})
})
三. 单元测试的写法
- 一般单元测试是白盒测试,知道代码怎么写的,按照写的代码来测试
- 如何测试读文件,初步想法先在某个路径创建一个文件,然后去读这个路径上的文件,如果能读出就表示正确
- 但是这样子做会有问题,依赖了外部环境,比如此路径已经有了这个文件,就会产生bug
- 单元测试不能与外界打交道,与外界打交道是集成测试应该做的
- 我们通过jest.mock来做,新建
__mocks__/fs.js
,实现一个最简单的例子
js
// __mocks__/fs.js
const fs = jest.genMockFromModule('fs')
fs.x = () => {
console.log('hi')
return 'xxx'
}
module.exports = fs;
// db.spec.js
const db = require('../db.js')
const fs = require('fs')
jest.mock('fs')
describe('db', fn => {
it('can read', ()=>{
expect(fs.x()).toBe('xxx')
})
})
- 测试读文件db.read,其实就是fs.read,所以需要在fs.read上做一些手脚
- 最终测试db.read的代码
js
// fs.js
// 这段代码是使用Jest测试框架中的jest.genMockFromModule函数来创建一个名为fs的模拟模块。在测试中,有时需要模拟一些Node.js核心模块,如文件系统(fs)模块,以便在测试过程中可以控制和检查模块的行为。
const fs = jest.genMockFromModule('fs')
// 这一行使用 jest.requireActual 获取实际的 fs 模块,保存在 _fs 变量中。这将允许模拟模块在需要时调用真实的 fs 模块功能。
const _fs = jest.requireActual('fs')
Object.assign(fs, _fs)
const mocks = {}
// 这个函数用于设置模拟的文件读取操作的期望结果。您可以通过调用 fs.setMock 来指定特定路径的文件读取应该返回的错误和数据。s
fs.setMock = (path, error, data) => {
mocks[path] = [error, data]
}
// 这个函数被用来模拟 fs.readFile 函数。它接受文件路径、选项和回调函数,检查是否有为特定路径设置的模拟结果。如果有模拟结果,
// 它会调用回调函数,并传递预先设置的错误和数据。如果没有模拟结果,它将调用真实的 _fs.readFile 函数。
fs.readFile = (path, options, callback) => {
if(callback === undefined) {
callback = options
}
if(path in mocks) {
callback(...mocks[path])
} else {
_fs.readFile(path, options, callback)
}
}
module.exports = fs;
- db.spec.js
js
const db = require('../db.js')
const fs = require('fs')
// 这段代码是使用Jest测试框架中的jest.mock函数来模拟Node.js的文件系统(fs)模块。jest.mock函数用于模拟模块,以便在测试过程中控制和检查模块的行为,而不实际执行真实的模块代码。
jest.mock('fs')
describe('db', fn => {
it('can read', async ()=>{
const data = [{title: 'hi', done: true}]
fs.setMock('/xxx', null, JSON.stringify(data))
const list = await db.read('/xxx')
expect(list).toStrictEqual(data)
})
})
四. 测试写文件行为
- 这里要注意,并非真正的去写一个文件,而是把文件写到任意路径
- fs.js
js
const writeMocks = {}
fs.setWriteFileMock = (path, fn) => {
writeMocks[path] = fn
}
fs.writeFile = (path, data, options, callback) => {
if (callback === undefined) {
callback = options
}
if(path in writeMocks) {
writeMocks[path](path, data, options, callback)
} else {
_fs.writeFile(path, data, options, callback)
}
}
- db.spec.js
js
it('can write', async () => {
let fakeFile
fs.setWriteFileMock('/yyy', (path, data, callback) => {
fakeFile = data
callback(null)
})
const list = [{title: '吃饭',done:true}, {title: '洗脚', done: true}]
await db.write(list, '/yyy')
expect(fakeFile).toBe(JSON.stringify(list))
})
五. 增加clearMocks
- 我们需要在每一个测试结束后清理测试数据,避免单元测试互相干扰
js
// fs.js
fs.clearMocks = () => {
readMocks = {}
writeMocks = {}
}
// db.spec.js
afterEach(()=>{
fs.clearMocks()
})
六. 最后总结
-
如何测试
- 选择测试工具:jest
- 设计测试用例
- 写测试,运行测试,改代码
-
单元测试、功能测试、集成测试的区别
- 单元测试不应该与外界打交道(那是集成测试要做的)
- 单元测试的对象是函数
- 功能测试的对象是模块
- 集成测试的对象是系统
- 一名优秀的开发者应该优先保证能做到单元测试