前端测试

测试

为什么项目需要测试

测试是完善的研发体系中不可或缺的一环,前端同样需要测试。一个项目最终会经过快速迭代走向以维护为主的状态,在合理的时机以合适的方式引入自动化能够让我们提前发现 bug,此时定位和修复的速度比开发完再被叫去修改 bug 要快许多;在项目重构或者开发人员发生变化也能保障预期功能的实现。

可测试方向

  • 界面回归测试: 测试界面是否正常,这是前端测试最基础的环节
  • 功能测试: 测试功能操作是否正常,由于涉及交互,这部分测试比界面测试会更复杂
  • 性能测试: 页面性能越来越受到关注,并且性能需要在开发过程中持续关注,否则很容易随着业务迭代而下降
  • 页面特征检测: 有些动态区域无法通过界面对比进行测试、也没有功能上的异常,但可能不符合需求。例如性能测试中移动端大图素材检测就是一种特征检测,另外常见的还有页面区块静态资源是否符合预期等等。

前端测试框架

前端测试工具也和前端的框架一样纷繁复杂,其中常见的测试工具,大致可分为测试框架、断言库、测试覆盖率工具等几类。常见的测试框架有JasmineMocha, 以及要介绍的 Jest

测试框架的作用是提供一些方便的语法来描述测试用例,以及对用例进行分组。
测试框架可分为两种,TDD (测试驱动开发)和 BDD (行为驱动开发)。

前端是一种特殊的GUI软件.

断言库

所谓断言,即提供语义化的方法,用于对参与测试的值做各种各样的判断,如不一致就抛出错误。常见的断言库有 should.jsChai.js

所有的测试用例(it块)都应该含有一句或多句的断言。它是编写测试用例的关键

1
expect(add(1, 1)).to.be.equal(2);

Jest

Jest 内置了常用的测试工具,如断言、测试覆盖率。

命名惯例

测试文件有如下常见的命名惯例。

  • __tests__ 目录下以 .js 为后缀的文件。
  • 以 .test.js(x) 或者 .spec.js(x) 为后缀的文件。

测试文件可以位于项目根目录下任何位置,可以通过 testMatch 修改默认配置。

编写测试

Jest 的作用是运行测试脚本。通常,测试脚本与所要测试的源码脚本同名,但是后缀名为 .test.js。测试脚本可以独立运行。

测试脚本里包含一个或多个 describe 块, 每个 describe 里应该包含一个或多个 test 块。

1
2
3
4
5
6
7
8
9
10
11
12
/* add.js */
function add(x, y) {
return x + y;
}
 
/* add.test.js */
const add = require('./add.js');
describe('加法函数的测试', function() {
it('1 加 1 应该等于 2', function() {
expect(add(1, 1)).toBe(2);
});
});

Jest 提供了内置的全局函数 expect 进行断言。

describe 称为测试套件(test suite),表示一组相关的测试。第一个参数是测试套件的名称,第二个参数是实际执行的函数。

test 称为测试用例(test case),表示一个单独的测试,是测试的最小单位。第一个参数是测试用例的名称,第二个参数是实际执行的函数。

1
2
3
it('work without done', () => {}); // 同步执行
 
it('work with done', (done) => {}); // 触发异步,执行 done() 通知 Jest 之行完毕

异步测试

使用单个参数调用 done, Jest 会等 done 回调函数执行结束后,结束测试。如果 done() 永远不被调用,这个测试将失败。

1
2
3
4
5
6
7
8
9
it('works with done', (done) => {
var x = true;
var f = function() {
x = false;
expect(x).toBeFalsy();
done(); // 通知 Jest 测试结束
};
setTimeout(f, 4000);
});

Jest 支持使用 Promise, 从测试返回一个 Promise, Jest 会等待这一 Promise resolve。 如果 Promise 被拒绝,则测试将自动失败。

1
2
3
4
5
6
7
8
9
10
11
12
const requestFn = (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('name');
}, 1000);
})
}
 
it('works with promises', () => {
expect.assertions(1); // 当前测试中执行断言的次数
return requestFn('mxl').then(data => expect(data).toBe('name'));
});

Jest 也支持 async/await 语法的测试,无需多余的操作,只要在 await 后进行断言即可。

可以使用 expect.assertions 来验证一定数量的断言被调用,以判断异步代码是否如预期一般执行。

测试组件

冒烟测试 验证一个组件渲染没有抛出异常,浅渲染并且测试一些输出,完整渲染测试组件的生命周期和状态的改变。

1
2
3
4
5
6
7
8
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
 
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});

初始化测试环境

使用 browser API 需要 mock,或者在测试前运行全局的配置,可以在 setup.js 文件里配置。

测试用例钩子

有时我们想在测试开始之前进行下环境的检查、或者在测试结束之后作一些清理操作,这就需要对用例进行预处理或后处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
describe('hooks', function() {
 
beforeAll(function() {
// 在本区块的所有测试用例之前执行
});
 
afterAll(function() {
// 在本区块的所有测试用例之后执行
});
 
beforeEach(function() {
// 在本区块的每个测试用例之前执行
});
 
afterEach(function() {
// 在本区块的每个测试用例之后执行
});
 
// test cases
});

测试用例管理

项目中测试用例很多,但希望只运行其中的几个,可以用only方法,describe 块和 test 块都允许调用 only方法,表示只运行某个测试套件或测试用例。

1
2
it.only('1 加 1 应该等于 2', () => { ... });
fit('1 加 1 应该等于 2', () => { ... });

此外,还有 skip 方法,表示跳过指定的测试套件或测试用例。

1
2
it.skip('1 加 1 应该等于 2', () => { ... });
xit('1 加 1 应该等于 2', () => { ... });

覆盖率报告

Jest 匹配文件生成测试报告,不需要额外的配置。

除了会再终端展示测试覆盖率情况,还会在项目下生产一个 coverage 目录。

1
npm test -- --coverage

小结

对于一些需求频繁变更、复用性较低的内容,编写测试用例得不偿失,适合引入测试用例的场景如下:

  • 需要长期维护的项目。它们需要测试来保障代码可维护性、功能的稳定性
  • 较为稳定的项目、或项目中较为稳定的部分。给它们写测试用例,维护成本低
  • 被多次复用的部分,比如一些通用组件和库函数。因为多处复用,更要保障质量

参考文献

react-test-demo(git上挺好的中文讲解)
jest(中文)
jest(英文)
Jest Snapshots and Beyond - React Conf 2017
The Difference Between TDD and BDD

原文地址:https://www.cnblogs.com/lixuekui/p/8663736.html