提到测试的时候,即使是最简单的一个代码块可能都让初学者不知所措。最常问的问题的是“我怎么知道要测试什么?”。如果你正在写一个 Web 应用,那么依次测试每个页面的用户交互方式,就是一个很好的开端了。
但 Web 应用也是由很多个函数和模块组成的代码单元,也是需要测试的。通常有两种情况:
对于上面两种场景,你可以把测试视为代码的一部分来编写。我所说的这些代码,是用来检查给定的函数是否产生预期输出结果的。 一个典型的测试流程如下:
即:输入 —— 预期输出 —— 验证结果。
下面来看一个例子:
// math.js
function sum (a, b) {return a + b
}function subtract (x, y) {return x - y
}module.exports = {sum,subtract
}
如何保证上面代码的正确性?
下面来写一段测试代码:
注意:通常将实际代码与测试文件隔离放置,方便管理维护。
// math.test.js
const { sum, subtract } = require('./math')// 测试示例1
// 运行结果
const result = sum(1, 2)
// 期望结果
const expected = 3if (result !== expected) {throw new Error(`1 + 2 应该等于 ${expected},但是结果却是 ${result}`)
}// 测试示例2
// 运行结果
const result2 = subtract(2, 1)
// 期望结果
const expected2 = 1if (result2 !== expected2) {throw new Error(`2 - 1 应该等于 ${expected2},但是结果却是 ${result2}`)
}
通过测试代码可以很方便的帮助验证代码的正确性。
之前示例的测试代码太过繁琐,可以思考一下能否封装的更简便一些,比如下面这样:
expect(sum(1, 2)).toBe(3)
expect(subtract(2, 1)).toBe(-1)
上面的测试代码就像自然语言说话一样,很舒服。
expect
称为断言函数:断定一个真实的结果是期望的结果。很多测试框架都有这个方法。
实现 expect 方法:
expect(sum(1, 2)).toBe(3)
expect(subtract(2, 1)).toBe(1)function expect(result) {return {toBe(actual) {if (result !== actual) {throw new Error(`预期值和实际值不相等,预期 ${result},结果确实 ${actual}`)}}}
}
增加错误提示信息:
// math.test.js
const { sum, subtract } = require('./math')test('测试加法', () => {expect(sum(1, 2)).toBe(3)
})test('测试减法', () => {expect(subtract(2, 1)).toBe(1)
})function test(description, callback) {try {callback()console.log(`${description} 通过测试`)} catch (err) {console.error(`${description} 没有通过测试:${err}`)}
}function expect(result) {return {toBe(actual) {if (result !== actual) {throw new Error(`预期值和实际值不相等,预期 ${result},结果确实 ${actual}`)}}}
}
Jest 介绍
Jest 是 Facebook 出品的一个 Javascript 开源测试框架。相对其他测试框架,其一大特点就是就是内置了常用的测试工具,比如零配置、自带断言、测试覆盖率工具等功能,实现了开箱即用。
Jest 适用但不局限于使用以下技术的项目:Babel,、TypeScript、 Node、 React、Angular、Vue 等。
Jest 主要特点:
安装 Jest 到项目中:
npm init -y
npm install -D jest
package.json 添加脚本:
"scripts": {"test": "jest"
},
编写实际代码:
// math.js
function sum(a, b) {return a + b
}function subtract(x, y) {return x - y
}module.exports = {sum,subtract
}
编写测试用例:
// math.test.js
const { sum, subtract } = require('./math')test('测试加法', () => {expect(sum(1, 2)).toBe(3)
})test('测试减法', () => {expect(subtract(2, 1)).toBe(1)
})
npm run test
运行测试命令。
.test.js
结尾的文件并运行test
、expect
等全局函数,所以在测试文件中可以直接使用由于文件中并没有引入 Jest 的方法,所以使用的时候 vscode 没有提供智能提示。
可以通过安装 jest 的类型声明文件 @types/jest
来解决。
npm i -D @types/jest
注意:@types/jest
必须安装到项目的根目录,并且以根目录的方式在 vscode 中打开,否则不生效。
或者说只要是 vscode 打开的项目根目录有 @types/jest
这个包就可以了。
这是因为 TS 是从项目根目录下的 node_modules 查找 @types 类型声明文件的。
Jest 配置Jest 默认提供了零配置的使用方式。如果要修改默认配置规则,可以生成并修改配置文件。
# 生成 jest 配置文件
npx jest --init# 配置文件的格式 ts or js
√ Would you like to use Typescript for the configuration file? ... no
# 测试环境 node 环境 或 jsdom 浏览器环境
√ Choose the test environment that will be used for testing » jsdom (browser-like)
# 是否需要 Jest 收集测试覆盖率报告
√ Do you want Jest to add coverage reports? ... no
# 用于统计测试覆盖率使用的引擎
# 目前最稳定是的 babel,v8 仍处于实验阶段,建议 node v14 版本以上使用
√ Which provider should be used to instrument code for coverage? » babel
# 是否在每次测试之前清除 mock 调用和相关实例
√ Automatically clear mock calls, instances and results before every test? ... yes
详细配置信息参考:配置 Jest(中文文档翻译不全,仅供参考)。
生成的配置文件 jest.config.js
中列出了一些配置选项,如果在生成时选择了非默认设置,就会取消注释覆盖默认配置,完整配置项请参考文档。
简单介绍几个:
/** For a detailed explanation regarding each configuration property, visit:* https://jestjs.io/docs/configuration*/module.exports = {// All imported modules in your tests should be mocked automatically// 自动 mock 所有导入的外部模块// automock: false,// Stop running tests after `n` failures// 在指定次数失败后停止运行测试// bail: 0,// The directory where Jest should store its cached dependency information// cacheDirectory: "C:\\Users\\Administrator\\AppData\\Local\\Temp\\jest",// Automatically clear mock calls, instances and results before every test// 在每个测试之间自动清除 mock 调用和实例clearMocks: true,// Indicates whether the coverage information should be collected while executing the test// 是否收集测试覆盖率信息// collectCoverage: false,// An array of glob patterns indicating a set of files for which coverage information should be collected// 一个 glob 模式数组,指示应该为其收集覆盖率信息的一组文件// collectCoverageFrom: undefined,// The directory where Jest should output its coverage files// 测试覆盖率报错文件输出的目录// coverageDirectory: undefined,// 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// 指示应该使用哪个引擎检测代码的覆盖率,默认是 babel,可选 v8,但是 v8 不太稳定,建议 Node 14 以上版本使用// coverageProvider: "babel",// A list of reporter names that Jest uses when writing coverage reports// coverageReporters: [// "json",// "text",// "lcov",// "clover"// ],// An object that configures minimum threshold enforcement for coverage results// coverageThreshold: undefined,// A path to a custom dependency extractor// dependencyExtractor: undefined,// Make calling deprecated APIs throw helpful error messages// errorOnDeprecated: false,// Force coverage collection from ignored files using an array of glob patterns// forceCoverageMatch: [],// 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.// 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",// "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: [// "
};
Jest 除了通过配置文件进行配置,还可以通过命令行参数进行配置。
参考:Jest CLI 选项 · Jest (jestjs.io)
有些配置只能在配置文件中使用,有些配置只能在命令行参数中使用,例如监视文件只用使用命令行参数 --watchAll
。
监视文件的更改并在任何更改时重新运行所有测试。
jest --watchALl
该模式需要 Git 支持。
git init
初始化仓库
监视 Git 仓库中更改的文件,并重新运行与已更改的文件相关的测试。
jest --watch
使用监视模式后,命令行中会显示辅助命令提示:
Watch Usage# 按 a 进入 a 模式:运行所有的测试。# a 进入,a 退出# 也可以使用 jest --watchAll 直接进入 a 模式# 只有 jest --watch 时才能使用› Press a to run all tests.# 按 f 进入 f 模式:只运行失败的测试。# f 进入,f 退出› Press f to run only failed tests.# 按 o 进入 o 模式:只运行与更改文件相关的测试。# 需要 Git 支持# 也可以使用 jest --watch 直接进入 o 模式# 只有 jest --watchAll 时才能使用› Press o to only run tests related to changed files.# 按 p 以文件名正则表达式模式进行过滤。# 只有 --watchAll 的时候 p 模式才可以使用# 注意:testRegex 将尝试使用绝对文件路径来检测测试文件,因此,具有名称与之匹配的文件夹将所有文件作为测试运行# testRegex 会忽略 testMatch› Press p to filter by a filename regex pattern.# 按 t 以测试名称(test 方法第一个参数)正则表达式模式进行过滤。› Press t to filter by a test name regex pattern.# 按 q 退出监视模式› Press q to quit watch mode.# 按 Enter 键触发测试运行› Press Enter to trigger a test run.
使用 ES6 模块
如果要在 Jest 测试中使用 ES6 模块,则需要使用-babel:
# 安装 babel 相关依赖
npm i -D babel-jest @babel/core @babel/preset-env
// babel.config.js
module.exports = {presets: [['@babel/preset-env', {targets: {node: 'current'}}]],
};
Jest 在运行测试的时候会自动找到 Babel 将 ES6 代码转换为 ES5 执行。
Jest 结合 Babel 的运行原理:运行测试之前,结合 Babel,先把代码做一次转化,模块被转换为了 CommonJS,运行转换之后的测试用例代码。