
ThinkDiff
2022/12/31阅读:35主题:红绯
Jest测试入门到使用
Jest 测试使用说明
什么是测试
作为开发来讲可以简单把代码分为两种,一种是业务代码,一种是业务无关的代码。测试代码就是典型的业务无关的代码。不过按照我的定义可以把测试代码定位为:可以保证业务代码是否能够按照预期运行的代码。有些人会把这个叫做 TDD,但是TDD(Test-Driven Development or Test-Driven Design)只是开发的一种思路或者说是习惯,先写测试用例,然后实现业务逻辑。
坦白讲,作为实用主义的程序员,先写测试用例还是后写测试用例或是不写测试,只要线上代码运行没问题就行了,测试不测试没那么重要。但是如果是写一个第三方库,业务中台代码,项目核心代码模块。此时的测试代码就十分有必要,如果你做开源如果没有 test
文件夹,可能你的方案就基本没人使用。
为什么做测试
自动化测试就是面向这些问题而生的。
测试分类
单元测试
单元测试是最基础的自动化测试,用来检测项目当中的最小可测单元,例如工具函数、基础组件等
集成测试
在单元测试的基础上,不同功能集成在一起,验证整体功能
E2E测试
相对真实、完整链路的模拟真实操作验证
压力测试
压测的目的是在于找到系统的瓶颈,一定是要确定系统某个方面达到瓶颈了。对于web应用,系统的瓶颈往往会是数据库;系统满负荷运作的时候,数据库的CPU或者是磁盘IO。
ui测试
对ui设计效果的验证,对数据渲染、交互上的验证
测试工具
-
单元测试(Unit Test)有 Jest, Mocha -
UI测试Test Render, Enzyme, -
端到端(E2E Test)Cypress.io、Nightwatch.js、Puppeteer、TestCafe
Jest基本使用
1. 安装依赖
# jest 开发依赖
npm install --save-dev jest
# babel 扩展包
npm install --save-dev babel-jest @babel/core @babel/preset-env
# Ts测试依赖
npm install --save-dev @babel/preset-typescript
# jest库 Ts 预处理器
npm install --save-dev ts-jest
# jest类型定义Types
npm install --save-dev @types/jest
# 另外一个类型定义模块
npm install --save-dev @jest/globals
2. 开发支持
我们先写一个两数相加的函数。 首先,创建 src/caculate.js
文件︰
const add = (a, b) => a + b
const remove = (a, b) => a - b
const multiply = (a, b) => a * b
const divide = (a, b) => a / b
module.exports = {
add, divide, multiply, remove,
}
然后,创建名为 test/sum.test.js
的文件。 此文件中将包含我们的实际测试︰
const { add, divide, multiply, remove } = require('../src/caculate')
describe('calculate module', () => {
test('add', () => {
expect(add(1, 2)).toBe(3)
})
test('remove', () => {
expect(remove(1, 2)).toBe(-1)
})
test('multiply', () => {
expect(multiply(1, 2)).toBe(2)
})
test('divide', () => {
expect(divide(1, 2)).toBe(0.5)
})
})
最后,运行 npx test
或 npm run test
,Jest将打印下面这个消息:
PASS test/caculate.test.js
3. 指定配置文件
基于您的项目,Jest将向您提出几个问题,并将创建一个基本的配置文件,每个选项都有一个简短的说明:
jest --init
最后会自动生成这样的配置文件,常用的带有中文说明
module.exports = {
// All imported modules in your tests should be mocked automatically
// 类型:boolean,默认:false,在每次测试前自动清除模拟的上下文
// automock: false,
// Stop running tests after `n` failures
// 失败多少次后停止测试
// bail: 0,
// The directory where Jest should store its cached dependency information
// 指定测试缓存文件夹
// cacheDirectory: "C:\\Users\\U\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls, instances, contexts and results before every test
// 在每一个测试之前自动清除模拟类、实例、上下文和结果
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// 类型:boolean,默认:false,是否开启 覆盖率
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// 生成的覆盖率文件的文件位置
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// 需要排除覆盖率文件夹
// coveragePathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// Indicates which provider should be used to instrument code for coverage
// 覆盖率测试需要的插件
// coverageProvider: "babel",
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// 最大测试Workers数
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
moduleFileExtensions: [
"js",
// "mjs",
// "cjs",
// "jsx",
"ts",
// "tsx",
// "json",
// "node"
],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// 用于模拟测试的测试环境,如果我们用到浏览器的环境(如:document),可以用 jsdom代替
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// 测试环境变量
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// 测试匹配规则
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)"
],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
testPathIgnorePatterns: [
"\\\\node_modules\\\\"
],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "\\\\node_modules\\\\",
// "\\.pnp\\.[^\\\\]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
}
Jest 匹配器
1. 常用匹配器
基本上函数名可以见名知意。
// 精确匹配
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
test('null', () => {
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
});
// 空值匹配
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
// 数值匹配
test('two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// toBe and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4);
});
// 字符串匹配
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
// 数组和可迭代对象
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'milk',
];
test('shoppingList数组中包含milk', () => {
expect(shoppingList).toContain('milk');
expect(new Set(shoppingList)).toContain('milk');
});
// Exception 匹配
function compileAndroidCode() {
throw new Error('you are using the wrong JDK!');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
// You can also use a string that must be contained in the error message or a regexp
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
// Or you can match an exact error mesage using a regexp like below
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK$/); // Test fails
expect(() => compileAndroidCode()).toThrow(/^you are using the wrong JDK!$/); // Test pass
});
2. 异步代码匹配
为你的测试返回一个Promise,则Jest会等待Promise的resove状态 如果 Promise 的状态变为 rejected, 测试将会失败。
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
// Async/Await 匹配
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (e) {
expect(e).toMatch('error');
}
});
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
Jest中级使用
全局函数
我们通过 sum.test.js
文件,发现了 describe
和 expect
,但我们并没有引入对应的函数,却能正常的使用,这是为什么呢?
实际上 Jest
会将这些方法和对象注入到测试文件的 全局环境
里,所以我们在使用的时候并不需要通过 import
或 require
当然,如果你一定要引用,可以这样引用:
import {describe, expect, test} from '@jest/globals
describe
describe: 描述块,将一组功能相关的测试用例组合在一块
it
it: 别名 test
, 用来存放测试用例,每一个it
就是一个测试用例
钩子函数
写测试的时候你经常需要在运行测试前做一些准备工作,和在运行测试后进行一些整理工作。 Jest 提供辅助函数来处理这个问题。
afterAll 和 beforeAll
afterAll: 所有的测试用例执行完 后
执行的方法,如果传入的回调函数返回值是 promise
或者 generator
, Jest
会等待 promise resolve
再继续执行。
beforeAll: 与 afterAll
相反, 所有的测试用例执行之 前
执行的方法
afterEach 和 beforeEach
afterEach: 也 afterAll
相比, afterEach
可以在每个测试完成 后
都运行一遍
beforeEach: beforeEach
可以在每个测试完成之 前
都运行一遍
// global.js
export const global = function(msg) {
console.log(msg)
}
// global.test.js
import { add } from '../src/caculate'
import { global } from '../src/global'
beforeAll(() => {
console.log('beforeAll')
})
afterAll(() => {
console.log('afterAll')
})
describe('calculate module', () => {
test('global start', () => {
expect(1).toBe(1)
global('global start')
})
test('global end', () => {
expect(1).toBe(1)
global('global end')
})
})
运行结果
加上 beforeEach
和 afterEach
import { global } from '../src/global'
beforeAll(() => {
console.log('beforeAll')
})
afterAll(() => {
console.log('afterAll')
})
beforeEach(() => {
console.log('beforeEach')
})
afterEach(() => {
console.log('afterEach')
})
describe('calculate module', () => {
test('global start', () => {
expect(1).toBe(1)
global('global start')
})
test('global end', () => {
expect(1).toBe(1)
global('global end')
})
})
运行结果,可以看到每一个 test
之前都运行了beforeEach
运行之后立马云 afterEach
Jest高级使用
模拟函数
Mock 函数允许你测试代码之间的连接——实现方式包括:擦除函数的实际实现、捕获对函数的调用 ( 以及在这些调用中传递的参数) 、在使用 new
实例化时捕获构造函数的实例、允许测试时配置返回值。
有两种方法可以模拟函数:要么在测试代码中创建一个 mock 函数,要么编写一个手动 mock
来覆盖模块依赖。
假设我们要测试函数 forEach
的内部实现,这个函数为传入的数组中的每个元素调用一次回调函数。
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
为了测试此函数,我们可以使用一个 mock 函数,然后检查 mock 函数的状态来确保回调函数如期调用。
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// 此 mock 函数被调用了两次
expect(mockCallback.mock.calls.length).toBe(2);
// 第一次调用函数时的第一个参数是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// 第二次调用函数时的第一个参数是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// 第一次函数调用的返回值是 42
expect(mockCallback.mock.results[0].value).toBe(42);
覆盖率报告
// jest.config.js
collectCoverage: true,
coverageDirectory: "coverage",
在 jest.config.js
中添加如下两个选项。运行测试
-
%stmts:是 语句覆盖率(statement coverage)
,是不是每个语句都执行了 -
%Branch:是 分支覆盖率(branch coverage)
,是不是每个if代码块都执行了 -
%Funcs:是 函数覆盖率(functioncoverage
,是不是每个函数都调用了 -
%Lines:是 行覆盖率(line coverage
,是不是每一行都执行了
查看 coverage
文件夹
打开index.html
查看结果
最后可以使用Circle
等集成工具自动化测试
项目代码地址
https://github.com/web0matrix/Web0matrix/tree/main/jest 欢迎点赞。模拟函数部分使用较少,还有待补充。
参考
-
https://mp.weixin.qq.com/s/2mcazw7-nL-0j1-0qkvsKQ -
https://jestjs.io/zh-Hans/docs/setup-teardown
作者介绍
