JEST单元测试

官方文档:https://jestjs.io/zh-Hans

一、什么是Jest

Jest 是 Facebook 开发的 Javascript 测试框架,用于创建、运行和编写测试的 JavaScript 库。Jest 作为 NPM 包发布,可以安装并运行在任何 JavaScript 项目中。Jest 是目前前端最流行的测试库之一。

与Mocha框架的对比。

框架断言异步代码覆盖率

Mocha不支持(需要其他库支持)友好不支持(需要其他库支持)

Jest默认支持友好支持

1. Mocha 生态好,但是需要较多的配置来实现高扩展性

2. Jest 开箱即用

无论是受欢迎度和写法上,Jest 都有很大的优势,因此推荐你使用开箱即用的 Jest

[if !supportLists]二、[endif]环境配置

1.安装JEST  

npm install --save-dev jest

2.生产基础配置文件

npm init jest@latest

3.使用Babel

 原因:nodejs 采用的是 CommonJS 的模块化规范,使用 require 引入模块;而 import 是 ES6 的模块化规范关键字。想要使用 import,必须引入 babel 转义支持,通过 babel 进行编译,使其变成 node 的模块化代码

解决:为了能使用这些新特性,我们就需要使用babel 把 ES6 转成 ES5 语法

// 安装依赖

npm install --save-dev babel-jest @babel/core @babel/preset-env

可以在工程的根目录下创建一个babel.config.js文件用于配置与你当前Node版本兼容的Babel:

babel.config.js

module.exports = {  presets: [['@babel/preset-env', {targets: {node: 'current'}}]],};

4.使用TypeScript

通过ts-jest实现,ts-jest 是一个支持 sourcemap 的 TypeScript 预处理器,让你使用 TypeScript 编写 Jest 测试项目

npm install --save-dev ts-jest

5.配置文件设置

(1)jest.config.js

module.exports = {

  'transform': {

    '^.+\\.jsx?$': 'babel-jest', // 这个是jest的默认配置

    '^.+\\.ts?$': 'ts-jest', // typescript转换

  },

  globals: {

    'ts-jest': {

      diagnostics: false

    }

  },

  moduleNameMapper: {

    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',

    '^@/(.*)$': '<rootDir>/src/$1',

    '^@customer/(.*)$': '<rootDir>/src/customer/uni/$1',

    '^@components/(.*)$': '<rootDir>/src/pages/components/$1',

    '^@pages/(.*)$': '<rootDir>/src/pages/$1',

  },

  // '^@scss/(.*)$': '<rootDir>/src/$1',

  testEnvironment: 'jsdom',

  // 'transformIgnorePatterns': [

  //   'node_modules/(?!(react-native|my-project|react-native-button)/)'

  // ]

  testPathIgnorePatterns: ['/node_modules/'],

  setupFiles: ['<rootDir>/tests/unit/setup.js'],

  collectCoverage: true,

  coverageDirectory: 'tests/unit/coverage',

  collectCoverageFrom: [

    'src/pages/smart/uni/launcher/**/*.{js,jsx,ts,tsx}', // 只收集 src/pages/uni 目录下的 JS 和 TS 文件的覆盖率

    '!**/node_modules/**', // 排除 node_modules 目录

    '!**/sysDefImg/**' // 排除 sysDefImg 目录

  ],

  resetMocks: true,

}

(2)babel.config.js

module.exports = {

'presets': [

[

'@babel/preset-env',

{

'targets': {

'node': true

}

}

]

]

}

(3)package.json

"scripts": {

    "jest": "jest  --watch"

  },

这样环境配置就完成了~~~可以着手写测试用例了

6.举个例子

我们先写一个两数相加的函数。

首先,创建sum.js 文件︰

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

然后,创建名为sum.test.js 的文件。 此文件中将包含我们的实际测试︰

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

随后,将下列配置内容添加到您的package.json:

{  "scripts": {    "test": "jest"  }}

最后,运行yarn test 或 npm run test ,Jest将打印下面这个消息:

PASS  ./sum.test.js✓ adds 1 + 2 to equal 3 (5ms)

您刚刚完成了您的首个Jest 测试!


三、相关参数

Jest会将这些方法和对象注入到测试文件的全局环境里, 所以你在使用的时候不再需要进行require或者import。 如果你习惯编写明确的导入,你可以在测试文件顶部添加 import {describe, expect, test} from '@jest/globals'。

import {expect, jest, test} from '@jest/globals';

1.describe(name, fn)

describe(name, fn) 是一个将多个相关的测试组合在一起的块。 比如,现在有一个myBeverage对象,描述了某种饮料好喝但是不酸,通过以下方式测试:

const myBeverage = {delicious: true,sour: false,

};

describe('my beverage', () => {  test('is not sour', () => {    expect(myBeverage.sour).toBeFalsy();  });

});

注意:这不是强制的,你甚至可以直接把 test 块直接写在最外层。 但是如果你习惯按组编写测试,使用 describe 包裹相关测试用例更加友好。

2.test(name, fn, timeout)

别名:it(name, fn, timeout)

第一个参数是测试名称; 第二个参数是包含测试期望的函数。 可选地传入第三个参数 timeout(毫秒) 指定测试超时时间。 The default timeout is 5 seconds.

当测试用例test过多时,可以进行测试分组,写到多个describe进行分组管理

更多参数详情参考官方文档:https://jestjs.io/zh-Hans/docs/api

3.常用的方法

(1).shallowMount和mount

---mount: 创建一个包含被挂载和渲染的 Vue 组件的 wrapper,它仅仅挂载当前实例

---shallowMount:和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,只挂载一个组件而不渲染其子组件 (即保留它们的存根),这个方法可以保证你关心的组件在渲染时没有同时将其子组件渲染,避免了子组件可能带来的副作用(比如Http请求等)

shallowMount和mount的区别:在文档中描述为"不同的是被存根的子组件",大白话就是shallowMount不会加载子组件,不会被子组件的行为属性影响该组件。

为什么使用shallowMount而不使用mount?

---我认为单元测试的重点在"单元"二字,而不是"测试",想测试子组件再为子组件写对应的测试代码即可(2).Wrapper:常见的有一下几种方法

Wrapper:Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。

Wrapper.vm:这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。

Wrapper.classes: 返回是否拥有该class的dom或者类名数组。

Wrapper.find:返回第一个满足条件的dom。

Wrapper.findAll:返回所有满足条件的dom。

Wrapper.html:返回html字符串。

Wrapper.text:返回内容字符串。

Wrapper.setData:设置该组件的初始data数据。

Wrapper.setProps:设置该组件的初始props数据。  (这是使用了,但没有效果)

Wrapper.trigger:用来触发事件。

原文链接:https://blog.csdn.net/qq_48959471/article/details/124620454


五、Expect断言

断言expect(value)

判断一个值是否满足条件,你会使用到expect函数。但你很少会单独调用expect函数,因为你通常会结合expect 和匹配器函数来断言某个值。六、匹配器的使用

常用的匹配器

最简单测试一个值的方法是使用精确匹配的方法。

test('two plus two is four', () => {  expect(2 + 2).toBe(4);});

在上面的代码中,expect (2 + 2) 返回了一个"预期"的对象。 你通常不会对这些期望对象调用过多的匹配器。 在此代码中,.toBe(4) 是匹配器。 当 Jest 运行时,它会跟踪所有失败的匹配器,以便它可以为你打印出很好的错误消息。

toBe使用 Object.is来进行精准匹配的测试。 If you want to check the value of an object, use toEqual:

test('对象赋值', () => {  const data = {one: 1};  data['two'] = 2;  expect(data).toEqual({one: 1, two: 2});});

toEqual 递归检查对象或数组的每个字段。

提示:

toEqualundefined忽略具有属性、undefined数组项、数组稀疏性或对象类型不匹配的对象键。考虑到这些,请toStrictEqual改为使用。

您还可以使用与匹配相反的not 来进行测试:

test('adding positive numbers is not zero', () => {  for (let a = 1; a < 10; a++) {    for (let b = 1; b < 10; b++) {      expect(a + b).not.toBe(0);    }  }});

1、真值

代码中的undefined, null, and false有不同含义,若你在测试时不想区分他们,可以用真值判断。 Jest提供helpers供你使用。

toBeNull 只匹配 null

toBeUndefined只匹配undefined

toBeDefined 与 toBeUndefined 相反

toBeTruthy 匹配任何 if 语句为真

toBeFalsy 匹配任何 if 语句为假

例如:

test('null', () => {  const n = null;  expect(n).toBeNull();  expect(n).toBeDefined();  expect(n).not.toBeUndefined();  expect(n).not.toBeTruthy();  expect(n).toBeFalsy();});test('zero', () => {  const z = 0;  expect(z).not.toBeNull();  expect(z).toBeDefined();  expect(z).not.toBeUndefined();  expect(z).not.toBeTruthy();  expect(z).toBeFalsy();});

你应该使用最精确匹配的匹配器来测试你期望你的代码能干什么。

2、数字

大多数的比较数字有等价的匹配器。

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);});

对于比较浮点数相等,使用toBeCloseTo 而不是 toEqual,因为你不希望测试取决于一个小小的舍入误差。

test('两个浮点数字相加', () => {  const value = 0.1 + 0.2;  //expect(value).toBe(0.3);这句会报错,因为浮点数有舍入误差  expect(value).toBeCloseTo(0.3); //这句可以运行});});

3、字符串

您可以检查对具有toMatch 正则表达式的字符串︰

test('there is no I in team', () => {  expect('team').not.toMatch(/I/);});test('but there is a "stop" in Christoph', () => {  expect('Christoph').toMatch(/stop/);});

4、数组和可迭代对象

你可以通过toContain来检查一个数组或可迭代对象是否包含某个特定项:

const shoppingList = [  'diapers',  'kleenex',  'trash bags',  'paper towels',  'milk',];test('shoppingList数组中包含milk', () => {  expect(shoppingList).toContain('milk');  expect(new Set(shoppingList)).toContain('milk');});

七、钩子函数的使用

钩子函数在父级分组可作用域子集,类似继承

钩子函数同级分组作用域互不干扰,各起作用

先执行外部的钩子函数,再执行内部的钩子函数

1、beforeAll(fn, timeout)

文件内所有测试开始前执行的钩子函数。

使用beforeAll 会非常方便你设置一些在测试用例之间共享的全局状态。

2、afterAll(fn, timeout)

文件内所有测试完成后执行的钩子函数。如果 afterAll 定义在 describe 块的内部,它将会在 describe 块结束时执行

3、beforeEach(fn, timeout)

文件内每个测试开始前执行的钩子函数。使用beforeEach 非常方便你重新设置一些全局状态在每个测试开始前

4、afterEach(fn, timeout)

文件内每个测试完成后执行的钩子函数。

使用afterEach 会非常方便你清理一些在每个测试中创建的临时状态。

八、测试覆盖率

运行命令:npx  jest --coverage

在软件测试中,代码覆盖率是一个重要的指标,用于衡量测试用例集对被测代码的覆盖程度。常见的代码覆盖率包括语句覆盖、分支覆盖、条件覆盖、路径覆盖等。

代码覆盖率并不是越高越好,需要根据项目的实际情况,例如开发成本、项目进度、质量要求等进行平衡。本文通过一个示例项目了解如何使用Jest编写单元测试,理解测试覆盖率报告中的数据含义,以及提升单元覆盖率的方法。

项目准备

首先,Jest 需要一个包作为环境,进行单元测试。

mkdir lab-test-coverage

pnpm init

注意:这里的项目目录名称不推荐用coverage,因为这个名字会与生成的测试报告重名。

接着,编写项目代码与单元测试:

// index.js

module.exports = function(a, b) {

if (a && b) {

return 1;

} else {

return 2;

}

}

function second() {

// The following line is written

// to show stmts and lines coverage differences

let v1 = 1, v2 = 2, v3 = 3;

return "";

}

针对index 的单元测试:

// __tests__/index.spec.js

const main = require('../index');

describe('Test coverage demo', () => {

it('Test main', () => {

   main(true, true);

});

it('Test main', () => {

main(true, false);

});

});

运行单元测试:

有了前面项目的基础,我们可以直接运行下面的命令来查看代码覆盖率:

jest --coverage

PASS  __tests__/index.spec.js

Test coverage demo

✓ Test main

✓ Test main

----------|---------|----------|---------|---------|-------------------

File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s

----------|---------|----------|---------|---------|-------------------

All files |      50 |      100 |      50 |   66.66 |                   

index.js |      50 |      100 |      50 |   66.66 | 12-13             

----------|---------|----------|---------|---------|-------------------

Test Suites: 1 passed, 1 total

Tests:       2 passed, 2 total

Snapshots:   0 total

Time:        0.257 s, estimated 1 s

Ran all test suites.

可以看到,这里有 Stmts、Branch、Funcs、 Lines四个百分比。

[if !supportLists]· [endif]Stmts  语句覆盖率 Branch分支覆盖率 Funcs 函数覆盖率 Lines 行覆盖率

这是命令行中输出的一个精简版的报告。

与此同时,可以看到在项目的根目录下生成了一个coverage 目录,内容如下:

coverage

├── clover.xml

├── coverage-final.json

├── lcov-report

│   ├── base.css

│   ├── block-navigation.js

│   ├── favicon.png

│   ├── index.html

│   ├── index.js.html

│   ├── prettify.css

│   ├── prettify.js

│   ├── sort-arrow-sprite.png

│   └── sorter.js

└── lcov.info

在浏览器中打开index.html,可以看到更详细的报告。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,858评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,372评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,282评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,842评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,857评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,679评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,406评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,311评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,767评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,090评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,785评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,420评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,988评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,101评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,298评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,033评论 2 355

推荐阅读更多精彩内容