Jest入门(一)

1,558 阅读8分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

为什么要做前端测试

首先我觉得前端并不是必须都要做单元测试或集成测试,因为写这些测试代码是需要花时间的,当项目比较简单或者时间紧任务重的时候,花时间写测试代码可能反而会影响开发效率。那么我们为什么要写测试代码呢?我想有以下几个原因

  1. 以终为始,知道自己要做什么,完成什么功能,达到哪些效果。而不是直接开发,可能对需求考虑不周,可以在在测试代码时再熟悉需求,明确自己的事,深入思考业务流程,让我们的代码写的更完善和规范,在之后开发过程中就减少产生的bug,提高产品质量。
  2. 有利于重构,一个项目的测试代码写的比较完善,重构过程中改动时可以迅速的通过测试代码是否通过来检查重构是否正确,很大程度提高重构效率,避免开发人员不知道是否影响旧功能,提心吊胆的上线,不敢改。
  3. 和注释一样,可能有助于后续者熟悉代码,注释很重要当然要写,帮助开发者理解某段代码;而测试代码更加重要,可以让开发者了解我们实现了哪些功能,我们想要哪些东西,有助于了解业务熟悉代码。

在前端测试中,最熟悉的概念莫过于TDD(测试驱动开发)和BDD(行为驱动开发)了,我们这里也来介绍一下他们吧

什么是TDD和BDD

什么是TDD呢?

TDD(Test Driven Development),即测试驱动开发,就是先编写好测试代码,根据测试代码去编写我们的逻辑代码,使其通过我们开始写的测试用例,是一种以测试来驱动开发过程的开发模式。

而提到TDD一般都是指单元测试,什么是单元测试呢?单元测试就是是指对软件中的最小可测试单元进行检查和验证。通俗的讲,在前端,单元可以理解为一个独立的模块文件,单元测试就是对这样一个模块文件的测试。对于每一个模块,功能都是相对独立的,我们可以首先编写测试代码,然后根据测试代码指导编写逻辑代码

什么是BDD呢

BDD(Behavior Driven Development),即行为驱动开发,简单的来说就是先编写业务逻辑代码,然后以使得所有业务逻辑按照预期结果执行为目的,编写测试代码,是一种以用户行为来驱动开发过程的开发模式。

提到BDD,这里的测试一般是指集成测试。什么是集成测试呢?集成测试就是指对软件中的所有模块按照设计要求进行组装为完整系统后,进行检查和验证。通俗的讲,在前端,集成测试可以理解为对多个模块实现的一个交互完整的交互流程进行测试。

对于多个模块(ES6模块)组成的系统,需要首先将交互行为完善,才能按照预期行为编写测试代码。

接下来我们一起来学学Jest的使用吧

Jest初识

首先我们应该创建一个项目,并安装好jest这个测试框架,具体步骤可以看Jest官网。我们以简单说说如下:

npm init -y
npm install --save-dev jest

然后在package.json中加入命令"test": "jest",之后运行npm run test就可以运行我们的测试代码了。

先写一个简单的例子吧。创建一个index.js文件,里面写上如下代码:

function sum(a, b) {
  return a + b;
}
module.exports = sum;

接下来,创建名为 sum.test.js 的文件。这个文件包含了实际测试内容:

// 引入我们带测试的功能函数
const sum = require('./sum');

/**
* test 函数:是Jest中的测试函数,第一个参数告诉你在测试哪个功能
 expect:是你执行函数后期望所得到的结果
 toBe:是一种匹配器,将与 expect返回的结果进行对比,判断测试是否通过
*/
test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

然后运行刚说的npm run test就会执行我们的测试代码了,我门第一个测试用例也就写成功了。这也是官网的例子。

细心的小伙伴可能会发现我们上面代码导出的是commonjs规范的代码,是不能在浏览器跑的,我们浏览器需要的是ES Modules 规范的代码,而如何实现这个功能呢,大家可以看看官网Babel的配置。

Jest简单配置

我们很时候需要自定义配置的,那么怎么做呢?执行命令jest --init生成Jest的基础配置,在根目录下会生成jest.config.js文件。可能会有以下几个属性,属性值会根据你们选择的选项而不同,我们就可以在该文件中自定义我们所需要的配置了。

// 自动清除每个测试之间的模拟调用和实例
  clearMocks: true,
  // 在执行测试时是否应收集覆盖率信息
  collectCoverage: true,
  // Jest应在其中输出其覆盖率文件的目录
  coverageDirectory: "coverage",
  // 将用于测试的测试环境
  testEnvironment: "jsdom",

Jest 中的匹配器

Common Matchers

toBe

toBe使用Object.is来测试精确的相等性。二者必须全等,如下所示,第一个通过,第二个不通过

test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
});

var a = { a: 1 }
var b = { a: 1 }
test('toBe', () => {
    expect(a).toBe(b); // 不通过测试
});

toEqual和toStrictEqual

如何应对上面的情况,检查对象类型呢?没错,使用toEqual和toStrictEqual将第二个用例的toBe改成toEqualtoStrictEqual后成功通过。那二者区别又在哪呢?

var a = { a: 1 }
var b = { a: 1 }
test('toEqual', () => {
    expect(a).toEqual(b);
});

官网对toEqual解释是:使用toEqual递归比较对象实例的所有属性(也称为“深度”相等)。它调用Object.is来比较原始值,这比(toBe)===严格相等运算符更适合测试。

toStrictEqual:使用toStrictEqual会先测试对象是否具有相同的类型和结构。

两者有什么不同呢?可以以下几条结合几个例子一起看

  1. 针对undefined、null这种属性值,toStrictEqual会严格对比每个属性和属性值,测试不会通过
var a = { a: undefined, b: 1 }
var b = { b: 1 }
test('toEqual', () => {
    expect(a).toEqual(b);
});
test('toStrictEqual', () => {
    expect(a).toStrictEqual(b);
});
// 测试结果
// ✓ toEqual
// ✕ toStrictEqual (2 ms)
  1. 针对数组缺省值,toStrictEqual不会通过
var a = [, 1]
var b = [undefined, 1]
test('toEqual', () => {
    expect(a).toEqual(b);
});
test('toStrictEqual', () => {
    expect(a).toStrictEqual(b);
});
// 测试结果
// ✓ toEqual
// ✕ toStrictEqual (2 ms)
  1. 检查对象属性类型是否相等时。具有a和b两个属性的实例A Class,不等于具有a和b两个属性的对象Object
class LaCroix {
    constructor(flavor) {
        this.flavor = flavor;
    }
}

describe('the La Croix cans on my desk', () => {
    test('are not semantically the same', () => {
        expect(new LaCroix('lemon')).toEqual({ flavor: 'lemon' });
        expect(new LaCroix('lemon')).not.toStrictEqual({ flavor: 'lemon' });
    });
});
// ✓ are not semantically the same

Truthiness

  1. toBeNull :判断是否是null
  2. toBeUndefined :判断是否是 undefined
  3. ​toBeDefined :是否被定为,他是toBeUndefined的反义词
  4. toBeTruthy:匹配值是否为真
  5. toBeFalsy :匹配值是否为假
test('null', () => {
    const n = null;
    expect(n).toBeNull(); // 是 null
    expect(n).toBeDefined(); // 被定义了
    expect(n).not.toBeUndefined(); // 被定义了
    expect(n).not.toBeTruthy(); // 不是真的
    expect(n).toBeFalsy(); // 假的
});

test('zero', () => {
    const z = 0;
    expect(z).not.toBeNull(); // 不是 null
    expect(z).toBeDefined(); // 被定义了
    expect(z).not.toBeUndefined(); // 被定义了
    expect(z).not.toBeTruthy(); // 不是真的
    expect(z).toBeFalsy(); // 不是真的
});
// 测试通过
// ✓ null
// ✓ zero (1 ms)

Numbers

这里是数字相关的匹配器。

  • toBeGreaterThan:大于
  • toBeGreaterThanOrEqual:大于或等于
  • toBeLessThan:小于
  • toBeLessThanOrEqual:小于或等于
  • toBeCloseTo:可以用来比较浮点数,指定位数
test('two plus two', () => {
    const value = 2 + 2;
    expect(value).toBeGreaterThan(3);
    expect(value).toBeGreaterThanOrEqual(3.5);
    expect(value).toBeLessThan(5);
    expect(value).toBeLessThanOrEqual(4.5);
  
    // toBe and toEqual are equivalent for numbers
    expect(value).toBe(4);
    expect(value).toEqual(4);
});

test('adding floating point numbers', () => {
    const value = 0.1 + 0.2;
    //expect(value).toBe(0.3);           This won't work because of rounding error
    expect(value).toBeCloseTo(0.3); // This works.
});

Strings

这里是字符串相关的匹配器。

  • toMatch:字符串正则匹配某个字符或者字符串序列
  • toContain:数组包含某项
test('there is no I in team', () => {
  expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
  expect('Christoph').toMatch(/stop/);
});
const shoppingList = [  'diapers',  'kleenex',  'trash bags',  'paper towels',  'milk',];

test('the shopping list has milk on it', () => {
  expect(shoppingList).toContain('milk');
  expect(new Set(shoppingList)).toContain('milk');
});

Exceptions

  • toThrow:如果要测试调用某个特定函数时是否抛出错误
function compileAndroidCode() {
  throw new Error('you are using the wrong JDK');
}

test('compiling android goes as expected', () => {
  expect(() => compileAndroidCode()).toThrow();
  expect(() => compileAndroidCode()).toThrow(Error);

  // You can also use the exact error message or a regexp
  expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
  expect(() => compileAndroidCode()).toThrow(/JDK/);
});

更多API见:www.jestjs.cn/docs/expect

简单的入门小知识就到这了,下一次分享异步、mock、dom以及快照等相关如何测试了

参考文章:

www.jestjs.cn/docs/gettin…

juejin.cn/post/684490…

coding.imooc.com/class/chapt…