单元测试框架Jest学习总结

[[toc]]

测试框架

Mocha+chai(断言库)

yarn add mocha chai -D

Jest

yarn add jest -D

适合场景

  • 业务比较复杂
  • 公司非常注重代码质量,想尽一切办法杜绝线上出bug
  • 需要长期维护的项目。它们需要测试来保障代码可维护性、功能的稳定性
  • 被多次复用的部分,比如一些通用组件和库函数。因为多处复用,更要保障质量
  • 开源项目

首先安装需要的包

基于vue

yarn add jest vue-jest babel-jest @vue/test-utils @types/jest -D

注意版本号之间兼容性问题

让Jest支持ES6语法

// .babelrc
"@babel/preset-env",
{
"targets": {
"browsers": [
"chrome >= 50"
],
"node": "current"
},
"modules": "auto" // 不能为true
}

测试的步骤

  • 写测试说明,针对你的每条测试说明测试了什么功能,预期结果是什么。
  • 写测试主体,通常是 输入 -> 输出。
  • 判断测试结果,拿输出和预期做对比。如果输出和预期相符,则测试通过。反之,不通过。

yarn add jest @types/jest babel-jest babel-core babel-preset-env regenerator-runtime -D

Jest本身是不支持es6的,但是在react中已经配置好babel等,可以直接使用ES6的语法特性进行单元测试

使用方式

expect(1).not.toBe(2)//判断不等

toBe() 绝对相等

toEqual() 判断对象或者数组是否相等

toBeNull()只匹配null

toContain()检测数组中是否包含特定某一项

toBeUndefined()只匹配undefined

toBeDefine()与toBeUndefined相反

toBeTruthy()匹配任何if语句为真

toBeFalsy()匹配任何if语句为假

toBeCloseTo(0.3) 浮点数判断相等

数字匹配器

toBeGreaterThan()大于

toBeGreaterThanOrEqual()大于或者等于

toBeLessThan()小于

toBeLessThanOrEqual()小于或等于

package里面关于jest的配置

//package.json
"scripts": {
"test": "jest",//全部测试
"app":"jest /test/app.test.js --watch" //单个测试 --watch持续检测
"test-watch": "jest --watchAll",// 所有文件持续检测
"test-with-coverage": "jest --coverage" // 文件覆盖
},
"jest": {
"collectCoverage":true, //查看覆盖的文件 `可以直接输入 jest --coverage 生成测试覆盖率报告`
"collectCoverageFrom": [ //哪些文件需要收集覆盖率信息
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"coverageDirectory": "tests/coverage", //覆盖的文件输入到tests文件夹下
"resolver": "jest-pnp-resolver",
"setupFiles": [
"react-app-polyfill/jsdom"
],
"testMatch": [ //设置识别哪些文件是测试文件
"<rootDir>/test/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/test/**/?(*.)(spec|test).{js,jsx,ts,tsx}"
],
"testEnvironment": "jsdom",
"testURL": "http://localhost",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss|less)$"
],
"moduleNameMapper": {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss|less)$": "identity-obj-proxy", //css module的问题
"^@(.*)$": "<rootDir>/src$1" //jest的别名设置
},
"moduleFileExtensions": [ //测试文件的类型
"js",
"ts",
"tsx",
"json",
"jsx",
"node"
]
}

Jest.config.js

module.exports = {
// preset: "ts-jest",
moduleFileExtensions: ["vue", "js", "jsx"],
testEnvironment: "jsdom",
transform: {
"^.+\\.js$": "babel-jest", // js 文件用 babel-jest 转换
"^.+\\.vue$": "vue-jest", // vue 文件用 vue-jest 转换
// "^.+\\.ts$": "ts-jest", // ts 文件用 ts-jest 转换
},
// // 例如,require('a') 语句会递归往上层的 node_modules 中寻找 a 模块
moduleDirectories: ["node_modules"],
// 匹配 __tests__ 目录下的 .js/.jxs 文件 或其他目录下的 xx.test.js xx.spec.js
testRegex: "(/__tests__/.(js|jsx)|(\\.|/)(test|spec))\\.(js|jsx)$",
// 支持源代码中相同的 `@` -> `src` 别名
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
};

代码覆盖率

可以查看你那些代码没有被覆盖,帮助你发现盲点

  • 在命令行中通过 “–coverage” flag 指定
  • 在 package.json 中手动配置

%stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?

%Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?

%Funcs函数覆盖率(function coverage):是不是每个函数都调用了?

%Lines行覆盖率(line coverage):是不是每一行都执行了?

Uncovered Line 是哪行没有被覆盖

专业术语里,把describe包含的块叫做suite,把it/test包含的块叫做specification,也简称为spec,在一个suite里面可以包含多个数量的spec,但是也要注意结构化语义化。

示例

编写测试文件时遵循的命名规范:测试文件的文件名 = 被测试模块名 + .test.js

//function.js
import axios from 'axios';
export default {
fetchUser() {
return axios.get('http://jsonplaceholder.typicode.com/users/1')
.then(res => res.data)
.catch(error => console.log(error));
},
sum(a, b) {
return a + b;
}
}
//function.test.js
import functions from '../src/functions';
test('fetchUser() 可以请求到一个含有name属性值为Leanne Graham的对象', () => {
expect.assertions(1);
return functions.fetchUser()
.then(data => {
expect(data.name).toBe('Leanne Graham');
});
});
it('fetchUser() 可以请求到一个含有name属性值为Leanne Graham的对象 async -- await', async () => {
expect.assertions(1);
const data = await functions.fetchUser();

expect(data.name).toBe('Leanne Graham');

});
describe('加法函数测试', () => {
it('1加2应该等于3', () => {
expect(functions.sum(1, 2)).toBe(3);
});
});
test('sum(2 + 2) 等于 4', () => {
expect(functions.sum(2, 2)).toBe(4);
});
test('sum(2 + 2) 等于 4', () => {
expect(functions.sum(2, 2)).not.toBe(1008611);
});
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a “stop” in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
test('测试浮点数是否相等', () => {
expect(0.003 + 0.01).toBeCloseTo(0.013); //这里不能使用toBe
});
test('对象判断是否相等', () => {
expect({test: "11111"}).toEqual({test: "11111"}); //
});


describe("筛选数组", () => {
test("it should filter by a search term (link)", () => {
const input = [
{id: 1, url: "https://www.url1.dev"},
{id: 2, url: "https://www.url2.dev"},
{id: 3, url: "https://www.link3.dev"}
];

const output = [{id: 3, url: "https://www.link3.dev"}];

expect(filterByTerm(input, "link")).toEqual(output);

expect(filterByTerm(input, "LINK")).toEqual(output);
});
});

function filterByTerm(inputArr, searchTerm) {
const regex = new RegExp(searchTerm, "i");
const a = inputArr.filter(function (arrayElement) {
return arrayElement.url.match(regex);
});
console.log(a);
return a
}

vue中的测试案例

// test.vue
<template>
<div>
<slot></slot>
</div>
</template>

// test.spec.js
import { shallowMount, mount } from "@vue/test-utils";
import test from "../test.vue";
import Vue from "vue";

describe("test.vue", () => {
let wrapper = shallowMount(test, {
slots: {
default: "测试案例",
},
});

it("设置slot", () => {
return Vue.nextTick().then(function() {
expect(wrapper.text()).toBe("测试案例");
});
});
});

未完待续…

参考文档

*ReactTestUtils

参考文档

参考文档2

jest 别名

Jest 入门教程