【单元测试方案】前端工程集成单元测试(Jest测试框架)

273 阅读5分钟

1 为什么要集成单元测试

测试最重要的是提升代码质量。通过测试用例,在上线发布前跑一下,可以杜绝各种疏忽而引起的功能bug。对于稳定持续发展的项目(比如组件库、封装库、共用方法库)集成单元测试是非常必要的,比开发完让QA测试来说开发效率更高。

2 什么是单元测试

对软件中的最小可测试单元进行测试,比如跑一个方法或API看是否符合预期

3 单元测试好处

1)预见性:保证代码是想要的结果 2)早期性:尽早发现问题  3)说明性:单元测试可以说明代码功能 4)保障性:对已有功能进行重构时,不会影响历史功能

4 选什么测试工具

前端测试工具和前端框架一样非常多,只有适合的才是最好的。目前常用的测试工具大致分为测试框架、断言库、测试覆盖率工具等几类。每一个又有很多工具,比如

  1. 测试框架:Jest、Mocha,测试框架的作用是提供一些方便的语法来描述测试用例,以及对用例进行分组
  2. 断言库:chai、should、expect等等,断言库主要提供语义化方法,用于对参与测试的值做各种各样的判断
  3. test runner  测试运行环境: karma
  4. mock库  sinon
  5. 代码覆盖率:用于统计测试用例对代码的测试情况,生成相应的报表,比如Istanbul

5 流行测试框架对比

衡量指标:运行环境的支持、并发的支持、库的集成度和社区活跃度。

  • Jest 它是 Facebook 出品的一个测试框架,最大的特点就是就是内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用。因此使用非常简单,安装依赖少,而且基本零配置
  • Mocha 强大的测试框架,常见的describe,beforeEach就来自这里,需要配合断言库等,安装依赖库及配置相对也比较多。

6 结论

通过对比,选择使用Jest

  • Jest测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了测试速度
  • 安装配置简单,非常容易上手,几乎是零配置的,通过npm 命令安装就可以直接运行了
  • Jest 内置了测试覆盖率工具istanbul,可以通过命令开启或者在 package.json 文件进行更详细的配置。运行 istanbul 除了会再终端展示测试覆盖率情况,还会在项目下生产一个 coverage 目录,内附一个测试覆盖率的报告,让我们可以清晰看到分支的代码的测试情况
  • 内部集成了断言库,不需要再引入第三方的断言库

总之, jest 包含了 karma + mocha + chai + sinon 的所有常用功能,零配置开箱即用,是一个很好的选择。

7 使用说明

  1. 前提环境准备:node环境(10+以上版本)
  2. 安装jest:
npm install jest -D
  1. package.json修改脚本命令
"scripts": {
    "test": "jest"
}

  1. 开始写测试之前的准备 4.1 需要明确的事
  • 测试对象是什么
  • 要测试什么功能
  • 预期想得到什么结果
  • 是否有外部依赖 4.2 执行步骤
  • 构造参数,创建外部依赖的mock,进行必要的设置
  • 用构造好的参数执行被测试代码
  • 用实际得到的结果与期望的结果比较
  • 移除在准备阶段创建的外部依赖一级其他影响 4.3 测试用例写法
  1. jest支持三种方式写测试代码
  • 放到__tests__目录文件夹下
  • 使用.spec.js后缀
  • 创建.test.js后缀 如下所示:

image.png

一般为了统一目录采用第一种方式,将所有的测试用例统一放到__tests__目录中。

  1. 测试代码结构
  • describe:将几个相关的测试放到一个组中,非必须
  • test:(别名it)测试用例,是测试的最小单位
  • expect:提供很多matcher来判断方法的返回值是否符合特定条件

image.png

  1. 异步方法测试
  • expect.assertions():确保异步方法被调用
  • done参数:参数.jest会等到done执行的时候才结束测试
  • promise的resolves
  • Async await支持
const {delay, delayPromise} = require('../index');
test('callback被执行', done => {
    expect.assertions(1);
    const callback = () => {
        console.log('callback expect');
        expect(true).toBe(true);
        done();
    };
    delay(callback);
});

test('delayPromise 测试 被执行', () => {
    expect.assertions(1);
    const callback = () => 1;
    return expect(delayPromise(callback)).resolves.toBe(1);
});

test('delayPromise async 测试 被执行', async () => {
    expect.assertions(1);
    const callback = () => 1;
    const res = await delayPromise(callback);
    expect(res).toBe(1);
})
  1. 依赖外部方法的测试
  • jest.fn()创建一个mock方法
const delayPromise = require('../index');

test('delayPromise 测试 被执行', () => {
    expect.assertions(1);
    const callback = jest.fn().mockReturnValue(1);
    return expect(delayPromise(callback)).resolves.toBe(1);
});
  • jest.mock()
// 通过mock方法对getData模块进行mock各自数据
jest.mock('../getData');
const normalizeData = require('../normalizeData');
const getData = require('../getData');
getData.mockReturnValue({
    name: 'xx'
})
test('normalizeData.js 测试', () => {
    const status = normalizeData().status;
    expect(status).toBe(0);
});

test('normalizeData.js 测试', () => {
    const status = normalizeData().data;
    expect(status).toEqual({
        name: 'xx'
    });
});
  • jest.spyOn():可以针对对象的属性进行监听
  • beforeEach/afterEach:在每个用例开始前/后都进行预处理
  • beforeAll/afterAll:对测试文件中所有的用例开始前/后进行统一的预处理

随机数测试demo

const getRandom = require('../getRandom');
// 每个测试用例执行前执行
let mockRandom = null;
beforeEach(() => {
    // 对Math的random方法进行监听
    mockRandom = jest.spyOn(Math, 'random');
});
// 每个测试用例执行后执行
afterEach(() => {
    // 恢复现场,防止其他测试用例执行失败
    mockRandom.mockRestore();
});

test('getRandom 小于10' ,() => {
    expect(getRandom()).toBeLessThan(10);
});


test('getRandom 大于等于0' ,() => {
    expect(getRandom()).toBeGreaterThanOrEqual(0);
});

test('Math.random 返回0.11,结果为1', () => {
    mockRandom.mockReturnValue(0.11);
    expect(getRandom()).toBe(1);
});

test('Math.random 返回1,结果为10', () => {
    mockRandom.mockReturnValue(1);
    expect(getRandom()).toBe(10);
})

执行npm run test结果如下

image.png

更多测试用例demo参考:github.com/mengmengzp/…