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 在页面上查看:

gtest windows下如何统计覆盖率 jest覆盖率_测试覆盖

gtest windows下如何统计覆盖率 jest覆盖率_上传_02

上传覆盖率

通常情况下不建议将测试覆盖率报告保存在项目仓库中:

# .gitignore
# jest 存放统计报告的目录
coverage

我们可以使用更专业的报告分析工具(平台)来帮助我们展示覆盖率报告。有两个网站可供选择:Codecov 和 Coveralls。这里以 Codecov 为例:

gtest windows下如何统计覆盖率 jest覆盖率_测试覆盖_03

这是Vue.js 发布在 codecov 上的测试覆盖率统计报告

首先,打开 Codecov 官网,绑定 Github 账号登录之后,选择要展示测试覆盖率的仓库

注意:上传报告的项目 git 必须是选择的 git 仓库,否则上传命令虽然不会报错,但并不会上传到 codecov 平台显示。

gtest windows下如何统计覆盖率 jest覆盖率_上传_04

拷贝 Codecov token(未上传过报告的仓库默认会显示入门指南,Step2 中有 token;上传过报告的仓库可以从 Settings 面板复制 token)

gtest windows下如何统计覆盖率 jest覆盖率_上传_05

然后安装 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 徽章,显示测试覆盖率,可以让其他开发者了解应用是否安全可靠。

gtest windows下如何统计覆盖率 jest覆盖率_上传_06

效果如下

gtest windows下如何统计覆盖率 jest覆盖率_上传_07

自动化测试和持续集成

如果每次修改代码之后,都手动进行单元测试,不仅加重工作量,而且容易出错,因此我们需要进行自动化测试,这就用到了持续集成。

持续集成是一种软件开发实践,每次集成都通过自动化的构建(包括编译、发布、测试等)来验证,从而尽早的发现代码中的错误。

此外项目如果接入持续集成,在多人开发同一个仓库时能起到很大的用途,比如每次 push 都能自动触发测试,测试没通过会发出警告。

或者如果需求采用 Issues + Merge Request (PR)来管理,每个需求一个 Issue + 一个分支,开发完成后提交 Merge Request,由项目 Owner 负责合并,项目质量将更有保障。

配置 Github Actions

可供选择的持续集成工具有 Gitlab CI、 Travis CICircle 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 的环境变量

gtest windows下如何统计覆盖率 jest覆盖率_自动化测试_08

提交代码

push 代码,触发 action

gtest windows下如何统计覆盖率 jest覆盖率_github_09

gtest windows下如何统计覆盖率 jest覆盖率_github_10

运行成功后,可以访问 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)

效果:

gtest windows下如何统计覆盖率 jest覆盖率_上传_11