Jest 自动化测试框架 笔记
Jest 前端自动化测试框架基础入门
自动化测试背景及原理
- 目录结构
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>math.js</title>
</head>
<body>
<h1>测试math.js 内的方法</h1>
<script src="./math.js"></script>
</body>
</html>
function add(a, b) {
return a + b;
}
function minus(a, b) {
return a - b;
}
function multi(a, b) {
return a * b;
}
math.test.js
function expect(result) {
return {
toBe: function(actual) {
if(result !== actual) {
throw new Error(`预期值和实际值不相等,预期${actual} 结果却是${result}`);
}
}
}
}
function test(desc, fn) {
try {
fn();
console.log(`${desc} 通过测试`)
}catch(e) {
console.log(`${desc} 没有通过测试 ${e}`)
}
}
test('测试加法 3 + 7', () => {
expect(add(3, 7)).toBe(10);
});
test('测试减法 3 - 3', () => {
expect(minus(3, 3)).toBe(0);
});
test('测试乘法 3 * 3', () => {
expect(multi(3, 3)).toBe(9);
});
前端自动化测试框架Jest
- 性能
- 尽可能的功能
- 易用性
jest测框架特点:
- 速度快
- API 简单
jest的API很简单,API数量少,简单的学习就可以使用 - 易配置
在项目中安装jest,通过jest配置文件简单的配置就可以使用jest了 - 隔离性好
jest中会有很多测试文件,但每个测试文件去执行的时候,它的执行环境都是隔离的就会避免不同的测试文件执行的时候之间会产生互相影响。 - 监控模式
使用监控模式可以使得我们更加灵活的使用测试用例。 - IDE整合
jest可以很方便的与ide整合,也就是在编辑器中做前端自动化测试也会变得很简单 - Snapshot
- 多项目并行
- 测试覆盖率
- Mock丰富
使用Jest 修改自动化测试样例
安装 node 环境
- 安装 jest
执行命令
npm init -y
npm install jest@24.8.0 -D
创建执行的测试文件
- 文件目录
- 修改package.json 文件
当运行 npm run test 命令的时候会执行 jest 的命令,jest 命令会找 这个目录下以 test.js 结尾的文件然后去运行这些文件。
"scripts": {
"test": "jest"
},
math.js
function add(a, b) {
return a + b;
}
function minus(a, b) {
return a - b;
}
function multi(a, b) {
return a * b;
}
// 导出add, minus, multi 方法
module.exports = {
add,
minus,
multi
}
math.test.js
function add(a, b) {
return a + b;
}
function minus(a, b) {
return a - b;
}
function multi(a, b) {
return a * b;
}
// 导出add, minus, multi 方法
// 引入 try 方法是关闭掉浏览器运行时检测没有module方法
try {
module.exports = {
add,
minus,
multi
}
}catch(e) {}
执行测试用例
- 执行命令 npm run test
- 执行结果
使用jest测试的疑问
jest 这个框架实际上在前端自动化测试中帮助我们完成的实际上两类的内容 单元测试 和 集成测试 。
所谓单元测试就是程序中的一个模块也就是最小单元,而集成测试可以理解为多个模块测试。
Jest 的简单配置
创建 jest.config.js 文件
- 执行命名:
npx jest --init
- jest.config.js 文件中的配置说明
collectCoverage: true, // 启用代码覆盖率
coverageDirectory: null, // 测试覆盖率报告生成的文件夹名
安装bable
npm install -D npm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D
创建 .babelrc 文件
// .babelrc
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "current"
}
}
]
]
}
// babel 执行过程
// npm run jest
// jest (babel-jest)
// babel-core
// 取 。babelrc 配置
// 在运行之前,结合babel,先把你的代码做一次转化
// 运行转化的过的测试用例代码
Jest 中的匹配器
监听所有test 文件的变化 实现自动执行
Jest 匹配器: https://jestjs.io/docs/using-matchers
// --------------- 和真假相关的匹配器---------------------
// test('测试 10 与 10 向匹配', () => {
// // toBe 匹配器
// const a = {one: 1}
// expect(a).toBe({one: 1});
// });
// test('测试 对象内容相等', () => {
// // toEqual 匹配器 值匹配内容
// const a = {one: 1}
// expect(a).toEqual({one: 1});
// });
// test('测试 toBeNull 匹配器', () => {
// // toBeNull 匹配器 值匹配内容是否为空
// const a = null;
// expect(a).toBeNull();
// });
// test('测试 undefined 匹配器', () => {
// // toBeUndefined 匹配器 值匹配内容是否未定义
// const a = undefined;
// expect(a).toBeUndefined();
// });
// test('测试 toBeDefined 匹配器', () => {
// // toBeDefined 匹配器 值匹配内容是否已定义
// const a = 1;
// expect(a).toBeDefined();
// });
// test('测试 toBeTrythy 匹配器', () => {
// // toBeTruthy 匹配器 值匹配内容是否为真
// const a = 1;
// expect(a).toBeTruthy();
// });
// test('测试 toBeFalsy 匹配器', () => {
// // toBeFalsy 匹配器 值匹配内容是否为假
// const a = false;
// expect(a).toBeFalsy();
// });
// test('测试 not 匹配器', () => {
// // not 匹配器 值匹配内容取反
// const a = true;
// expect(a).not.toBeFalsy();
// });
// --------------- 和数字相关的匹配器---------------------
// test('测试 toBeGreaterThan 匹配器', () => {
// // toBeGreaterThan 匹配器 值匹配内容是否大于预期
// const a = 10;
// expect(a).toBeGreaterThan(5);
// });
// test('测试 toBeLessThan 匹配器', () => {
// // toBeLessThan 匹配器 值匹配内容是否小于预期
// const a = 10;
// expect(a).toBeLessThan(11);
// });
// test('测试 toBeGreaterThanOrEqual 匹配器', () => {
// // toBeGreaterThanOrEqual 匹配器 值匹配内容是否大于等于预期
// // toBeLessThanOrEqusl 匹配器 值匹配内容是否小于等于预期
// const a = 10;
// expect(a).toBeGreaterThan(10);
// });
// test('测试 toBeCloseTo 匹配器', () => {
// // toBeCloseTo 匹配器 值匹配内容是小数时,可以解决 浮点精度问题
// const firstNumber = 0.1;
// const secondNumber = 0.2;
// // expect(firstNumber + secondNumber).toEqual(0.3);
// expect(firstNumber + secondNumber).toBeCloseTo(0.3);
// });
// --------------- 和字符串相关的匹配器---------------------
// test('测试 toMatch 匹配器', () => {
// // toBeLessThan 匹配器 值匹配内容是否包含预期值
// const str = "https://www.baidu.com";
// expect(str).toMatch("baidu");
// });
// --------------- 和数组串相关的匹配器---------------------
// test('测试 toContain 匹配器', () => {
// // toContain 匹配器 值匹配内容是否包含预期值
// const arr = ['xiaoming', 'xiaolong', 'xiaowen']
// expect(arr).toContain("xiaoming")
// });
// --------------- 和异常相关的匹配器---------------------
// 创建一个抛出异常的函数
const throwNewErrorFunc = () => {
throw new Error("this is a new error")
}
test('测试 toThrow 匹配器', () => {
// toThrow 匹配器 值匹配内容是否包含预异常
expect(throwNewErrorFunc).toThrow()
});
jest 命令行工具的使用
1.在VSCode 中使用Ctrl + Shif + P 命令
2.在、然后搜索 install code command 安装
3. 可以在dos 窗口使用 code 命令 打开 vscode
如果在 package.json 中 配置了
"test": "jest --watchAll"
那么在执行 npm run test 命令后会在 jest 命令行中提示一下内容:
Watch Usage
› Press f to run only failed tests.
› Press o to only run tests related to changed files.
› 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.
命令解读:
- Press f to run only failed tests.
按下 f 指挥执行失败的测试用例 - Press o to only run tests related to changed files.
按o只运行与已更改文件相关的测试;- package.json 使用的是 : “test”: “jest --wathAll” 就可以使用 o 命令
这个命令会记录你改了哪些文件,如果没有引入一些工具包去记录文件的改变时就会报错;
所以想使用 o 命令,那么就需要使用git 工具来管理我们的代码,使用git来管理我们的代码就可以记录代码的变化,那么就可以比对文件的更变,否则 jest 就无法确定代码改变的地方,实际上 jest -o 命令是跟 git 之间有一个绑定关系的。 - package.json 使用的是 : “test”: “jest --wacth” 那么 jest 的 o 命令就不可以使用,因为 jest 会默认进入 o 命令 模式。
- package.json 使用的是 : “test”: “jest --wathAll” 就可以使用 o 命令
- Press p to filter by a filename regex pattern.
按 p 通过文件名正则表达式模式进行过滤。 - Press t to filter by a test name regex pattern.
按 t 按测试名称正则表达式模式进行筛选。
异步代码的测试方法
// fetchData.js
import axios from 'axios';
// export const fetchData = (fn) => {
// axios.get("http://www.dell-lee.com/react/api/demo.json").then((response) => {
// fn(response.data)
// })
// }
export const fetchData = () => {
return axios.get("http://www.dell-lee.com/react/api/demo.json");
}
// fetchData.test.js
import { fetchData } from "./fetchData";
// test('fetchData 返回结果为 { success: true }', async() => {
// const response = await fetchData();
// expect(response).toEqual({
// success: true
// })
// })
test("fetchData 返回结果为 404", async() => {
expect.assertions(1); // 要求下面的代码必须执行一个 expext 方法
try {
await fetchData();
}catch(e) {
expect(e.toString()).toEqual("Not Found");
}
});
Jest 中的钩子函数
Jest中的钩子函数就是指在 jest 执行过程中的某一些具体时刻会被 jest 自动调用l的一些函数。
关于钩子函数的作用域:
- beforeAll、afterAll 会对同级或子集中的 describe 函数内的 describe 函数进行挂钩操作。
- beforeEach、afterEach 会对同级或子集中的 describe 函数内的所有 test函数进行挂钩操作。
说明:挂钩操作就是指该函数在执行前或执行后要执行的函数。
jest 中常用的函数
describe(name, fn)
describe.each(table)(name, fn, timeout)
describe.only(name, fn)
describe.only.each(table)(name, fn)
describe.skip(name, fn)
describe.skip.each(table)(name, fn)
beforeAll(fn, timeout)
afterAll(fn, timeout)
beforEach(fn, timeout)
afterEach(fn, timeout)
test(name, fn, timeout)
test.each(table)(name, fn, timeout)
test.skip();
test.skip.each(table)(name, fn)
test.only(table)(name, fn)
test.only.each(table)(name, fn)
test.todo(name)
test.concurrent(name, fn, timeout)
test.concurrent.each(table)(name, fn, timeout)
test.concurrent.only.each(table)(name, fn)
test.concurrent.skip.each(table)(name, fn)
jest 钩子函数的简单使用
// Counter.test.js
export default class Counter {
constructor() {
this.number = 0;
}
addOne() {
this.number +=1;
}
minusOne() {
this.number -= 1;
}
}
// Counter.js
import Counter from "./Counter";
let counter;
describe("钩子函数测试", () => { // 使用 describe 可以很好的对测试用例做一个分组
beforeAll(() => {
console.log("最近一层的 describe 开始执行时会执行到 beforeAll")
counter = new Counter();
})
afterAll(() => {
console.log("最近一层的describe 执行结束时会执行到 afterAll ")
})
beforeEach(() => {
console.log("在同级以及子集中 describe 函数中的每个 test 函数前会执行 beforeEach ")
})
afterEach(() => {
console.log("在同级以及子集中 describe 函数中的每个 test 函数执行结束后会执行 afterEach ")
})
test("测试 Counter 中的 addOne 方法", () =>{
counter.addOne();
expect(counter.number).toBe(1);
});
test("测试 Counter 中的 addOne 方法", () =>{
counter.minusOne();
expect(counter.number).toBe(0);
});
});
Jest 中的Mock
jest 中的 mock 使用场景:
- 测试函数是否被执行或函数执行的次数,mock函数可以帮助我们捕获函数的调用
- 可以让我们自由的设置返回结果
- 改变内部函数的实现,
// demo.js
export const runCallback = (callback) =>{
callback();
}
export const createObject = (classItem) => {
new classItem();
}
export const getData = () => {
return axios.get('/').then(res => res.data);
}
// demo.test.js
import { runCallback, createObject } from "./demo";
test("测试 runCallback 函数", () => {
const func = jest.fn(() => {
return "456"
}); // mock 函数可以捕获函数的调用
func.mockReturnValueOnce('xiao') // 让func函数模拟一次返回值为 "xiao"
func.mockReturnValueOnce('ming')
// func.mockReturnValue("hihi"); // 设置 funcc 模拟函数的返回值为 "hihi"
runCallback(func);
runCallback(func);
runCallback(func);
expect(func).toBeCalled(); // 判断 func 函数是否被执行过
console.log(func.mock)
// expect(runCallback(func)).toBe("hello")
})
test("测试 createObject", () => {
const func = jest.fn();
createObject(func);
console.log(func.mock)
})
test.only('测试 getDate', async () => {
axios.get.mockResolvedValue({data: 'hello'}) // 使用jest 模拟 get方法的返回数据
// axios.get.mockResolvedValueOnce({data: 'hello'}) // 模拟一次
await getData().then((data) => {
expect(data).toBe('hello');
});
});
/*
mock 属性
{
calls: [ [], [], [] ], // calls 表示函数被调用的次数以及调用时传入的参数
instances: [ undefined, undefined, undefined ], // instances 表示被调用的次数以及每次被调用 this 的执行
invocationCallOrder: [ 1, 2, 3 ], // invocationCallOrder 指的是这个函数有可能被多次传入同一个方法或不同的方法中,那么传入的执行顺序就别被记录到invocationCallOrder中
results: [ // result 表示被被调用的次数以及每次执行返回的结果
{ type: 'return', value: 'xiao' }, //
{ type: 'return', value: 'ming' },
{ type: 'return', value: '456' }
]
}
console.log demo.test.js:21
{
calls: [ [] ],
instances: [ mockConstructor {} ],
invocationCallOrder: [ 4 ],
results: [ { type: 'return', value: undefined } ]
}
*/
Jest 难点进阶
snapshot 快照测试
使用外部存储式 snapshot
snapshot很适合测试 配置文件的测试用例。
实际上在 Vue和 Rect 组件的UI展示使用 snapshot 进行组件的单元测试也会很好用。
实战:
// demo.js
export const generateConfig = () => {
return {
server: "http://localhost",
port: 8000,
domain: "localhost",
time: new Date()
}
}
export const generatAnotherConfig = () => {
return {
server: "http://localhost",
port: 8000,
domain: "localhost",
time: new Date()
}
}
// demo.test.js
import { generatAnotherConfig, generateConfig } from "./demo";
test("测试 gennerateConfig 函数", () => {
expect(generateConfig()).toMatchSnapshot({
time: expect.any(Date)
});
});
test("测试 generateAnthoerConfig 函数", () => {
expect(generatAnotherConfig()).toMatchSnapshot({
time: expect.any(Date)
});
});
说明:
- 使用 snapshot 中的 toMatchSnapshot 函数时 会在 根目录下创建一个
__snapshots__
文件 用来存放 toMathSnapshot 函数的快照文件。 - 当快照文件更变时,执行测试用例会运行失败 可以时 u 命令更新快照文件
snapshots failed from 1 test suite. Inspect your code changes or pressu
to update them. - 如果多个测试用例的快照都发生更变,又不想更新全部配置文件的快照时就可以使用 i 命令 来逐个更新。
Press i to update failing snapshots interactively.
使用行内式 snapshot
安装 prettier@1.18.2
npm install prettier@1.18.2 --save
测试文件:
import { generatAnotherConfig, generateConfig } from "./demo";
test("测试 generateAnthoerConfig 函数", () => {
expect(generatAnotherConfig()).toMatchInlineSnapshot(
{
time: expect.any(Date)
},
// 下面的的数据格式就是由 toMatchInlineSnapshot() 函数自动创建的行内式测试快照
`
Object {
"domain": "localhost",
"port": 8000,
"server": "http://localhost",
"time": Any<Date>,
}
`
);
});
mock 深入学习
mock timers
ES6 中类的测试
之所以对函数做 mock,是因为我们函数的一些执行是能够被追述的,那么时候时候调用了它,调用了几次,每次调用的参数是什么,只有在 jest 中 jest.fn 这样的方式对函数做了包装之后这个函数才是可追述的。有时在 jest 测试中需要发一些 axios 请求,但实际上前端测试之中我们并不是很关注后端返回的接口成功还是失败或数据请求格式对与不对,我们更多的精力是投入到当我们拿到正确或失败的数据后,后面的逻辑或前端的逻辑是否能够正确的运行,所以一般情况下我们对这些数据请求的内容进行 mock。
React 中的 TDD 与单元测试
什么是TDD
Test Driven Development (简称TDD) 测试驱动开发
TDD 的开发流程(Red-Green Develepment)
- 编写测试用例
- 运行测试,测试用例无法通过测试。
- 编写代码,使测试用例通过测试。
- 优化代码,完成开发。
- 重复上述步骤。
TDD
- 长期减少回归 bug。
- 代码质量更好(组织,可维护性)。
- 测试覆盖率高。
- 错误测试代码不容易出现。
React 环境配置 Jest
创建 React 项目
- 安装 react 脚手架工具
npm install create-react-app -g
- 使用 react 脚手架工具创建 jest-react 项目
creact-react-app jest-react
TDD 与 BDD 对比
TDD | BDD |
---|---|
先写测试在写代码 | 先写代码再写测试 |
一般结合单元测试使用,是白盒测试 | 一般结合继承测试,是黑盒测试 |
测试重点在代码 | 测试重点在 UI (DOM) |
安全感低 | 安全感高 |
速度快 | 速度慢 |