DOM 测试
直接操作 DOM 的代码通常被认为难以测试,因为操作 DOM 就要使用浏览器环境的 DOM Api,而 Jest 是运行在 Node 环境中的。
例如:
// 操作 DOM 的函数
function renderHtml() {
const div = document.createElement('div')
div.innerHTML = `
<h1>Hello World</h1>
`
document.body.appendChild(div)
}
实际上 Jest 内部引入了一个第三方包 jsdom,这个工具模拟了一套浏览器环境的 DOM Api。
Jest 通过 jsdom 模拟了 DOM 环境,可以让开发着像在浏览器中一样直接每个 DOM Api。
test('DOM Testing', () => {
renderHtml()
// console.log(document.body.innerHTML)
expect(document.querySelector('h1').innerHTML).toBe('Hello World')
})
Vue 组件测试
可以进行 DOM 测试,就可以很方便的进行 Vue、React 组件测试,因为它们最终都会渲染成 DOM。
下例使用字符串模板方式创建一个 Vue 组件:
// 注意:本例使用 vue2,当前 Vue 默认版本已更新为 Vue3
// 安装 vue2: npm i vue@2
import Vue from 'vue/dist/vue'
function renderVueComponent() {
document.body.innerHTML = `
<div id="app"></div>
`
new Vue({
template: `
<div id="app">
<h1>{{ message }}</h1>
</div>
`,
data: {
message: 'Hello World'
}
}).$mount('#app')
}
test('Vue Testing', () => {
renderVueComponent()
console.log(document.body.innerHTML)
expect(document.body.innerHTML).toMatch(/Hello World/)
})
快照测试
官方文档:Snapshot Testing
要想确保 UI 界面不会意外改变,快照测试是一个非常有用的工具。
典型的快照测试用例渲染一个 UI 组件,拍摄快照,然后与存储在快照文件中的内容进行比较,如果两个快照不匹配,要么就是发生了意外更改,要不就是需要更新快照版本。
使用快照
// snapshot.test.js
function renderHtml() {
const div = document.createElement('div')
div.innerHTML = `
<h1>Hello World</h1>
`
document.body.appendChild(div)
}
test('Snapshot Testing', () => {
renderHtml()
// 第一次运行测试,会生成快照文件,存储 expect() 传入的字符串
// 下次运行测试的时候会和快照文件进行比对
expect(document.body.innerHTML).toMatchSnapshot()
})
首次运行测试,会在项目根目录下创建 __snapshots__
文件夹,在该目录看下,创建基于测试文件名称的快照文件 snapshot.test.js.snap
,内容如下:
exports[`Snapshot Testing 1`] = `
"<div>
<h1>Hello World</h1>
</div>"
`;
更新快照
如果组件发生了有意义的更改,快照文件不会同步更新,需要我们手动更新快照文件:
# 在项目中运行 jest 更新快照命令
npx jest --updateSnapshot
# 或使用简写
npx jest -u
这个命令将会更新测试中全部的快照文件,所以在此之前你应该修复那些额外的失败的快照测试错误,以避免生成包含错误行为的快照。
你也可以通过参数 --testNamePattern
仅为该模式匹配的测试用例重新生成快照。
# 仅更新匹配测试用例名称成功的测试的快照
npx jest -u --testNamePattern=Snap
# 或使用简写
npx jest -u -t=Snap
测试覆盖率
测试覆盖率(test coverage)是衡量软件测试完整性的一个重要指标。掌握测试覆盖率数据,有利于客观认识软件质量,正确了解测试状态,有效改进测试工作。
Jest 测试覆盖率相关配置
// jest.config.js
module.exports = {
// ...
// 是否收集测试覆盖率信息
// collectCoverage: false,
collectCoverage: true,
// 一个 glob 模式数组,指示应该为其收集覆盖率信息的一组文件
// collectCoverageFrom: undefined,
collectCoverageFrom: [
'**/*.{js,jsx}',
'!**/node_modules/**',
'!**/vendor/**'
],
// 测试覆盖率报错文件输出的目录
// coverageDirectory: undefined,
coverageDirectory: 'coverage',
// 忽略测试覆盖率统计的文件
// coveragePathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// 指示应该使用哪个引擎检测代码的覆盖率,默认是 babel,可选 v8,但是 v8 不太稳定,建议 Node 14 以上版本使用
coverageProvider: "babel",
// A list of reporter names that Jest uses when writing coverage reports
// Jest 在编写覆盖率报告时使用的报告人姓名列表
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// 覆盖率阈值,如果没有达到阈值则测试失败
// coverageThreshold: undefined,
coverageThreshold: {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": -10
},
"./src/components/": {
"branches": 40,
"statements": 40
},
"./src/reducers/**/*.js": {
"statements": 90
},
"./src/api/very-important-module.js": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
},
// 通常,在收集代码覆盖率时会忽略测试文件。
// 使用此选项,可以覆盖此行为,并在代码覆盖率中包含被忽略的文件
// forceCoverageMatch: [],
forceCoverageMatch: ['**/*.t.js'],
};
覆盖率报告
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 80 | 100 |
foo.js | 0 | 0 | 0 | 0 |
math.js | 100 | 100 | 100 | 100 |
user.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
指标 | 说明 |
% Stmts(statement coverage) | 语句覆盖率:是不是每个语句都执行了 |
% Branch(branch coverage) | 分支覆盖率:是不是每个 if 代码块都执行了 |
% Funcs(function coverage) | 函数覆盖率:是不是每个函数都调用了 |
% Lines(line coverage) | 行覆盖率:是不是每一行都执行了 |
报告页面
如果配置了启用统计代码覆盖率,每次运行 Jest 都会生成报告,也可以使用命令行选项手动指定:jest --coverage
。
建议:项目中新增一个脚本运行 Jest 测试,使用命令参数指定统计代码覆盖率,避免开发时每次运行测试都要统计,消耗性能。
可以打开 coverage\lcov-report\index.html
在页面上查看:
上传覆盖率
通常情况下不建议将测试覆盖率报告保存在项目仓库中:
# .gitignore
# jest 存放统计报告的目录
coverage
我们可以使用更专业的报告分析工具(平台)来帮助我们展示覆盖率报告。有两个网站可供选择:Codecov 和 Coveralls。这里以 Codecov 为例:
首先,打开 Codecov 官网,绑定 Github 账号登录之后,选择要展示测试覆盖率的仓库
注意:上传报告的项目 git 必须是选择的 git 仓库,否则上传命令虽然不会报错,但并不会上传到 codecov 平台显示。
拷贝 Codecov token(未上传过报告的仓库默认会显示入门指南,Step2 中有 token;上传过报告的仓库可以从 Settings 面板复制 token)
然后安装 Codecov:
npm i -D codecov
# 或者安装到全局
# npm i -g codecov
生成测试覆盖率报告:
# coverage 是运行 `jest -- coverrage` 的脚本
npm run coverage
将测试覆盖率报告上传到 codecov:
# 运行项目安装的 codecov 上传报告
npx codecov --token=xxx
# 使用全局安装的 codecov
codecov --token=xxx
在 Settings 面板复制 Badge 链接到 README.md
中可以展示 codecov 徽章,显示测试覆盖率,可以让其他开发者了解应用是否安全可靠。
效果如下
自动化测试和持续集成
如果每次修改代码之后,都手动进行单元测试,不仅加重工作量,而且容易出错,因此我们需要进行自动化测试,这就用到了持续集成。
持续集成是一种软件开发实践,每次集成都通过自动化的构建(包括编译、发布、测试等)来验证,从而尽早的发现代码中的错误。
此外项目如果接入持续集成,在多人开发同一个仓库时能起到很大的用途,比如每次 push 都能自动触发测试,测试没通过会发出警告。
或者如果需求采用 Issues + Merge Request (PR)来管理,每个需求一个 Issue + 一个分支,开发完成后提交 Merge Request,由项目 Owner 负责合并,项目质量将更有保障。
配置 Github Actions
可供选择的持续集成工具有 Gitlab CI、 Travis CI 、 Circle CI、GitHub Actions 等。这里以 GitHub Actions 为例。
项目根目录新建目录和文件 .github/workflows/main.yml
:
# .github\workflows\main.yml
name: Publish And Deploy Demo
on:
# 当提交 main 分支的代码的时候触发 action
push:
branches:
- main
# 或对 main 分支进行 pull request 的时候
pull_request:
branches:
- main
jobs:
build-and-deploy:
# 运行环境
runs-on: ubuntu-latest
steps:
# 下载仓库源码
- name: Checkout
uses: actions/checkout@main
# 安装依赖 & 运行测试并生成覆盖率报告 & 项目打包
- name: Install and Build
run: |
npm install
npm run coverage
npm run build
# 发布到 GitHub Pages
- name: Deploy
uses: JamesIves/github-pages-deploy-action@4.1.0
with:
branch: gh-pages # The branch the action should deploy to.
folder: dist # The folder the action should deploy.
# 上传测试覆盖率报告到 codecov
- name: codecov
# 使用 codecov 官方提供的 action
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
如果测试失败,自动构建就会中断,不会部署 Github Pages 和上传覆盖率报告。
Github 添加存储 codecov Token 的环境变量
提交代码
push 代码,触发 action
运行成功后,可以访问 Codecov 查看覆盖率报告。
Github Pages
修改打包路径
如果要使用 Github Pages 可能需要修改打包路径,因为 Github Pages 访问地址默认会带二级域名(仓库名),例如http://xxx.github.io/vue-testing-demo/
,修改打包路径:
// vue.config.js
module.exports = {
publicPath: '/vue-testing-demo'
}
指定托管 Github Pages 的分支
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CEmYkFcd-1648047936990)(.\imgs\27.png)]
添加工作流程状态徽章
添加 Github Actions 状态徽章,向 README.md 添加链接:![example workflow](https://github.com/<OWNER>/<REPOSITORY>/actions/workflows/<WORKFLOW_FILE>/badge.svg)
-
<WORKFLOW_FILE>
:.github/workflows/
目录下的.yml
工作流程文件名。 -
<OWNER>
:github
组织名 -
<REPOSITORY>
:仓库名
例如:![](https://github.com/<你的 github 用户名>/<你的仓库名>/actions/workflows/main.yml/badge.svg)
效果: