估计很多前端都没学过单元测试~

大家好,我是若川。最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。已进行四个月了,很多小伙伴表示收获颇丰。

想学源码,极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。同时推荐参与源码共读活动。


前言

对于现在的前端工程,一个标准完整的项目,通常情况单元测试是非常必要的。但很多时候我们只是完成了项目而忽略了项目测试。我认为其中一个很大的原因是很多人对单元测试认知不够,因此我写了这边文章,一方面期望通过这篇文章让你对单元测试有一个初步认识。另一个方面希望通过代码示例,让你掌握写单元测试实践能力。

前端为什么需要单元测试?

  1. 必要性:JavaScript 缺少类型检查,编译期间无法定位到错误,单元测试可以帮助你测试多种异常情况。

  2. 正确性:测试可以验证代码的正确性,在上线前做到心里有底。

  3. 自动化:通过 console 虽然可以打印出内部信息,但是这是一次性的事情,下次测试还需要从头来过,效率不能得到保证。通过编写测试用例,可以做到一次编写,多次运行。

  4. 保证重构:互联网行业产品迭代速度很快,迭代后必然存在代码重构的过程,那怎么才能保证重构后代码的质量呢?有测试用例做后盾,就可以大胆的进行重构。

现状

下面是一份抽样调查片段,抽样依据如下:

  • 向 200 名相关者发出在线问卷调查,其中 70 人回答了问卷中的问题,前端人数占 81.16%,如果你有兴趣的话,也可以帮我填一下调查问卷 (https://www.wjx.cn/vm/Ombu9q1.aspx)

  • 数据收集日期:2021.09.21—2021.10.08

  • 目标群体:所有开发人员

  • 组织规模:不到 50 人,50 到 100人, 100人以上

你执行过 JavaScript 单元测试吗?

4a6a061fd013a913d4c276e67ed1f384.png

调查中的另一个有趣的见解是,在大型组织中单元测试更受欢迎。其中一个原因可能是,由于大型组织需要处理大规模的产品,以及频繁的功能迭代吧。这种持续的迭代方式,迫使他们进行自动化测试的投入。更具体地说,单元测试有助于增强产品的整体质量。

a5f7538c885fd8ef930511d0a43ddeda.png

另外,报告显示超 80% 人认为单元测试可以有效的提高质量,超 60% 人使用过 Jest 去编写前端单元测试,超 40% 的人认为单元测试覆盖率是重要的且覆盖率应该大于 80%。

常见单元测试工具

目前用的最多的前端单元测试框架主要有 Mocha (https://mochajs.cn/)、Jest (https://www.jestjs.cn/),但我推荐你使用 Jest,因为 Jest 和 Mocha 相比,无论从 github starts & issues 量,npm下载量相比,都有明显优势。

github stars 以及 npm 下载量的实时数据,参见:jest vs mocha (https://www.npmtrends.com/jest-vs-mocha) 截图日期为 2021.11.25

Github stars & issues

745b5cafd715a6d067b66c53826b163c.png

npm 下载量

Jest 的下载量较大,一部分原因是因为 create-react-app 脚手架默认内置了 Jest, 而大部分 react 项目都是用它生成的。

90eb2abd65d687834a5b100b047ca9de.png

从 github starts & issues 以及 npm 下载量角度来看,Jest 的关注度更高,社区也更活跃

框架对比

框架断言异步代码覆盖率
Mocha不支持(需要其他库支持)友好不支持(需要其他库支持)
Jest默认支持友好支持
  • Mocha 生态好,但是需要较多的配置来实现高扩展性

  • Jest 开箱即用

比如对 sum 函数写用例

./sum.js

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

Mocha + Chai 方式

Mocha 需要引入 chai 或则其他断言库去断言, 如果你需要查看覆盖率报告你还需要安装 nyc 或者其他覆盖率工具

./test/sum.test.js

const { expect, assert } = require('chai');
const sum = require('../sum');describe('sum', function() {it('adds 1 + 2 to equal 3', () => {assert(sum(1, 2) === 3);});
});

Jest 方式

Jest 默认支持断言,同时默认支持覆盖率测试

./test/sum.test.js

const sum = require('./sum');describe('sum function test', () => {it('sum(1, 2) === 3', () => {expect(sum(1, 2)).toBe(3);});// 这里 test 和 it 没有明显区别,it 是指: it should xxx, test 是指 test xxxtest('sum(1, 2) === 3', () => {expect(sum(1, 2)).toBe(3);});
})

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

如何开始?

1.安装依赖

npm install --save-dev jest

2.简单的例子

首先,创建一个 sum.js 文件

./sum.js

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

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

./test/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"},
}

运行 npm run test ,jest 将打印下面这个消息

a1a7c92bc6420467e6c3ea0f01c8b8aa.png

3.不支持部分 ES6 语法

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

如以下文件改写成 ES6 写法后,运行 npm run test将会报错

./sum.js

export function sum(a, b) {return a + b;
}

./test/sum.test.js

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

报错

09e33b093e5a6da51a6a7a6e1e9cea7a.png

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

解决办法

安装依赖

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

根目录加入.babelrc

{   "presets": ["@babel/preset-env"] }

再次运行 npm run test ,问题解决

ddc43706ec034bd2c9cf763b975194b7.png

原理

jest 运行时内部先执行( jest-babel ),检测是否安装 babel-core,然后取 .babelrc 中的配置运行测试之前结合 babel 先把测试用例代码转换一遍然后再进行测试

4.测试 ts 文件

jest 需要借助 .babelrc 去解析 TypeScript 文件再进行测试

安装依赖

npm install --save-dev @babel/preset-typescript

**改写 **.babelrc

{   "presets": ["@babel/preset-env", "@babel/preset-typescript"] }

为了解决编辑器对 jest 断言方法的类型报错,如 test、expect 的报错,你还需要安装

npm install --save-dev @types/jest

./get.ts

/*** 访问嵌套对象,避免代码中出现类似 user && user.personalInfo ? user.personalInfo.name : null 的代码*/
export function get<T>(object: any, path: Array<number | string>, defaultValue?: T) : T {const result = path.reduce((obj, key) => obj !== undefined ? obj[key] : undefined, object);return result !== undefined ? result : defaultValue;
}

./test/get.test.ts

import { get } from './get';test('测试嵌套对象存在的可枚举属性 line1', () => {expect(get({id: 101,email: 'jack@dev.com',personalInfo: {name: 'Jack',address: {line1: 'westwish st',line2: 'washmasher',city: 'wallas',state: 'WX'}}}, ['personalInfo', 'address', 'line1'])).toBe('westwish st');
});

运行 npm run test

4ac475eaf418bf85b5c305b4affd59e2.png

5.持续监听

为了提高效率,可以通过加启动参数的方式让 jest 持续监听文件的修改,而不需要每次修改完再重新执行测试用例

改写 package.json

"scripts": {     "test": "jest --watchAll"   },

效果

42f66b472b0f80a8c7f0c5208dd1c27a.png

5.生成测试覆盖率报告

什么是单元测试覆盖率?

单元测试覆盖率是一种软件测试的度量指标,指在所有功能代码中,完成了单元测试的代码所占的比例。有很多自动化测试框架工具可以提供这一统计数据,其中最基础的计算方式为:

单元测试覆盖率 = 被测代码行数 / 参测代码总行数 * 100%

如何生成?

加入 jest.config.js  文件

module.exports = {// 是否显示覆盖率报告collectCoverage: true,// 告诉 jest 哪些文件需要经过单元测试测试collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'],
}

再次运行效果

38975e7da596ab75ce013c2e7ce2fb7f.png

参数解读

参数名含义说明
% stmts语句覆盖率是不是每个语句都执行了?
% Branch分支覆盖率是不是每个 if 代码块都执行了?
% Funcs函数覆盖率是不是每个函数都调用了?
% Lines行覆盖率是不是每一行都执行了?

设置单元测试覆盖率阀值

个人认为既然在项目中集成了单元测试,那么非常有必要关注单元测试的质量,而覆盖率则一定程度上客观的反映了单测的质量,同时我们还可以通过设置单元测试阀值的方式提示用户是否达到了预期质量。

jest.config.js  文件

module.exports = {collectCoverage: true, // 是否显示覆盖率报告collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'], // 告诉 jest 哪些文件需要经过单元测试测试coverageThreshold: {global: {statements: 90, // 保证每个语句都执行了functions: 90, // 保证每个函数都调用了branches: 90, // 保证每个 if 等分支代码都执行了},},

上述阀值要求我们的测试用例足够充分,如果我们的用例没有足够充分,则下面的报错将会帮助你去完善

d2eb5b497cf8c56fc302b69d9fc86911.png

6.如何编写单元测试

下面我们以 fetchEnv 方法作为案例,编写一套完整的单元测试用例供读者参考

编写 fetchEnv 方法

./src/utils/fetchEnv.ts  文件

/*** 环境参数枚举*/enum IEnvEnum {DEV = 'dev', // 开发TEST = 'test', // 测试PRE = 'pre', // 预发PROD = 'prod', // 生产
}/*** 根据链接获取当前环境参数* @param {string?} url 资源链接* @returns {IEnvEnum} 环境参数*/
export function fetchEnv(url: string): IEnvEnum {const envs = [IEnvEnum.DEV, IEnvEnum.TEST, IEnvEnum.PRE];return envs.find((env) => url.includes(env)) || IEnvEnum.PROD;
}

编写对应的单元测试

./test/fetchEnv.test.ts  文件

import { fetchEnv } from '../src/utils/fetchEnv';describe('fetchEnv', () => {it ('判断是否 dev 环境', () => {expect(fetchEnv('https://www.imooc.dev.com/')).toBe('dev');});it ('判断是否 test 环境', () => {expect(fetchEnv('https://www.imooc.test.com/')).toBe('test');});it ('判断是否 pre 环境', () => {expect(fetchEnv('https://www.imooc.pre.com/')).toBe('pre');});it ('判断是否 prod 环境', () => {expect(fetchEnv('https://www.imooc.prod.com/')).toBe('prod');});it ('判断是否 prod 环境', () => {expect(fetchEnv('https://www.imooc.com/')).toBe('prod');});
});

执行结果

0e5b535d7dda6f4ad6860d180abfa892.png

7.常用断言方法

关于断言方法有很多,这里仅摘出常用方法,如果你想了解更多,你可以去 Jest 官网 API (https://www.jestjs.cn/docs/expect) 部分查看

.not 修饰符允许你测试结果不等于某个值的情况

./test/sum.test.js

import { sum } from './sum';test('sum(2, 4) 不等于 5', () => {expect(sum(2, 4)).not.toBe(5);
})

.toEqual 匹配器会递归的检查对象所有属性和属性值是否相等,常用来检测引用类型

./src/utils/userInfo.js

export const getUserInfo = () => {return {name: 'moji',age: 24,}
}

./test/userInfo.test.js

import { getUserInfo }  from '../src/userInfo.js';test('getUserInfo()返回的对象深度相等', () => {expect(getUserInfo()).toEqual(getUserInfo());
})test('getUserInfo()返回的对象内存地址不同', () => {expect(getUserInfo()).not.toBe(getUserInfo());
})

.toHaveLength 可以很方便的用来测试字符串和数组类型的长度是否满足预期

./src/utils/getIntArray.js

export const getIntArray = (num) => {if (!Number.isInteger(num)) {throw Error('"getIntArray"只接受整数类型的参数');}return [...new Array(num).keys()];
};

./test/getIntArray.test.js

./test/getIntArray.test.js
import { getIntArray }  from '../src/utils/getIntArray';test('getIntArray(3)返回的数组长度应该为3', () => {expect(getIntArray(3)).toHaveLength(3);
})

.toThorw 能够让我们测试被测试方法是否按照预期抛出异常

但是需要注意的是:我们必须使用一个函数将被测试的函数做一个包装,正如下面 getIntArrayWrapFn 所做的那样,否则会因为函数抛出错误导致该断言失败。

./test/getIntArray.test.js

import { getIntArray }  from '../src/utils/getIntArray';test('getIntArray(3.3)应该抛出错误', () => {function getIntArrayWrapFn() {getIntArray(3.3);}expect(getIntArrayWrapFn).toThrow('"getIntArray"只接受整数类型的参数');
})

.toMatch 传入一个正则表达式,它允许我们来进行字符串类型的正则匹配

./test/userInfo.test.js

import { getUserInfo }  from '../src/utils/userInfo.js';test("getUserInfo().name 应该包含'mo'", () => {expect(getUserInfo().name).toMatch(/mo/i);
})

测试异步函数

./servers/fetchUser.js

/** * 获取用户信息
*/
export const fetchUser = () => {return new Promise((resole) => {setTimeout(() => {resole({name: 'moji',age: 24,})}, 2000)})
}

./test/fetchUser.test.js

import { fetchUser } from '../src/fetchUser';test('fetchUser() 可以请求到一个用户名字为 moji', async () => {const data =  await fetchUser();expect(data.name).toBe('moji')
})

这里你可能看到这样一条报错

59c7eee7d3bb58f122d5dc3df71892d4.png

这是因为 @babel/preset-env 不支持 async await 导致的,这时候就需要对 babel 配置进行增强,可以安装 @babel/plugin-transform-runtime 这个插件解决

npm install --save-dev @babel/plugin-transform-runtime

同时改写 .babelrc

{"presets": ["@babel/preset-env", "@babel/preset-typescript"],"plugins": ["@babel/plugin-transform-runtime"]
}

再次运行就不会出现报错了

cc0ea7f01199b827ac939d3f7f169fa7.png

.toContain 匹配对象中是否包含

./test/toContain.test.js

const names = ['liam', 'jim', 'bart'];test('匹配对象是否包含', () => {expect(names).toContain('jim');
})

检查一些特殊的值(null,undefined 和 boolean)

toBeNull 仅匹配 null
toBeUndefined 仅匹配 undefined
toBeDefined 与…相反 toBeUndefined
toBeTruthy 匹配 if 语句视为 true 的任何内容
toBeFalsy 匹配 if 语句视为 false 的任何内容检查数字类型(number)
toBeGreaterThan 大于
toBeGreaterThanOrEqual 至少(大于等于)
toBeLessThan 小于
toBeLessThanOrEqual 最多(小于等于)
toBeCloseTo 用来匹配浮点数(带小数点的相等)

总结

以上就是文章全部内容,相信你阅读完这篇文章后,已经掌握了前端单元测试的基本知识,甚至可以按照文章教学步骤,现在就可以在你的项目中接入单元测试。同时在阅读过程中如果你有任何问题,或者有更好见解,更好的框架推荐,欢迎你在评论区留言!

也许在你阅读这篇文章之前,你本身就已掌握前端单元测试技能了,甚至已经是这个领域的大牛了,那么首先我感到非常荣幸,同时也诚恳的邀请你在评论区提出宝贵意见,我在这里提前说声谢谢!

最后感谢你在百忙之中抽出时间阅读这篇文章,送人玫瑰,手有余香,如果你觉得文章对你有所帮助,希望可以帮我点个赞!

最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。

推荐阅读

整整4个月了,尽全力组织了源码共读活动~
我历时3年才写了10余篇源码文章,但收获了100w+阅读

老姚浅谈:怎么学JavaScript?

我在阿里招前端,该怎么帮你(可进面试群)

786d3f26ab03340e8d1385c49740e9cc.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》10余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助1000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

dad245802d13cf9753f0e5e9e43d8d07.png

识别方二维码加我微信、拉你进源码共读

今日话题

略。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275137.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xd可以用ui动效效果吗_通过动画使UI设计栩栩如生:Adobe XD和After Effects

xd可以用ui动效效果吗Note — If you don’t fancy splashing out on an Adobe license, you can trial their products for 14 days each. That should give you more than enough time to play, check it out.注意—如果您不愿意花钱购买Adobe许可证&#xff0c;则可以分别试…

第十二周编程总结

这个作业属于那个课程C语言程序设计II这个作业要求在哪里https://pintia.cn/problem-sets/1127748174659035136/problems/1127749414029729792我在这个课程的目标是更好的学习函数这个作业在那个具体方面帮助我实现目锻炼了我的编程能力参考文献c语言程序设计26-1 计算最长的字…

可能是全网首个前端源码共读活动,诚邀加入学习

大家好&#xff0c;我是若川。从8月份到现在11月结束了。每周一期&#xff0c;一起读200行左右的源码&#xff0c;撰写辅助文章&#xff0c;截止到现在整整4个月了。由写有《学习源码整体架构系列》20余篇的若川【若川视野公众号号主】倾力组织&#xff0c;召集了各大厂对于源码…

现代游戏中的UX趋势

ux设计中的各种地图游戏UX (GAMES UX) Even though websites and games have matured side-by-side over the past few decades, games have a long and detailed history of user experience. Sure, it was scrappy and fairly rudimentary initially, but the only way you c…

你提交代码前没有校验?巧用gitHooks解决

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。已进行四个月了&#xff0c;很多小伙伴表示收获颇丰。想学源码&#xff0c;极力推荐订阅我写的《学习…

Linux下自动化测试环境的搭建

1.安装Linux虚拟机&#xff0c;详情参考 https://blog.csdn.net/qq_22770715/article/details/78558374 https://www.cnblogs.com/Q277227/p/8176564.html 1.1 需要确定IP &#xff0c;使用 ifconfig 1.2 linux的用户名跟密码&#xff1b; 1.3 确定可以远程ssh登录&…

code craft_以Craft.io为先—关于我们行业的实践职业道路的系列

code craft重点 (Top highlight)For the past two decades, digital product design / UX has been shifting to become a more strategic discipline within organizations. Partially because business leaders have started to pay attention to how design-driven companie…

Nginx+httpd反代实现动静分离

什么是动静分离为了提高网站的响应速度&#xff0c;减轻程序服务器&#xff08;apachephp&#xff0c;nginxphp等&#xff09;的负载&#xff0c;对于静态资源比如图片&#xff0c;js&#xff0c;css&#xff0c;html等静态文件&#xff0c;我们可以在反向代理服务器中设置&…

(建议收藏)前端面试必问的十六条HTTP网络知识体系

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。已进行四个月了&#xff0c;很多小伙伴表示收获颇丰。想学源码&#xff0c;极力推荐订阅我写的《学习…

多边形的时针方向与法线方向

从相反的法线方向观察&#xff0c;顺时针还是逆时针是相反的。 多边形的时针方向与法线方向的关系呈右手法则关系。 GoogleEarth中的面具有时针方向&#xff0c;法线方向为正向&#xff0c;反之为负向 GoogleEarth的垂面在法线方向为亮色&#xff0c;反向为暗色 GoogleEarth的水…

裂墙推荐!再也不用求后端给接口了...

大家好&#xff0c;我是若川。今天咱们来介绍一款强大的云服务平台&#xff01;MemFire Cloud注册即享5GB存储空间、每月100万读额度和每月10万写额度。平台入口&#xff1a;https://memfiredb.com/今天&#xff08;12月10号&#xff09;还有限时的送书活动&#xff01;感兴趣的…

1.今日标签:视频价值一千字

I love the App Store. It looks and works better than ever. But also, I love tricky design challenges. How do you improve something that already works great?我喜欢App Store。 它的外观和工作比以往更好。 但是我也很棘手 设计挑战。 您如何改善已经很好的工作&a…

蚂蚁金服疯了吗?大动作,非裁员,年底全员涨薪又涨假期!!!

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。以下分享一篇水文&#…

书呆子rico_寻找设计和类型书呆子的清道夫

书呆子ricoI studied graphic design at an art school where typography was a core focus. I took 3 levels of typography classes and nearly lost my mind! But even before I studied type, I had a soft spot for signage. It’s one of the themes I enjoy shooting mo…

WebStorm 和 VsCode 的结合体来了!

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。周末分享下简单的文章~每…

设计的概念以及含义_什么是设计概念? 以及为什么您应该始终从一个开始

设计的概念以及含义We work on designs almost every day, and we use different design methods to make the design usable, aesthetically appealing, and likable. But, many times, those well-crafted designs fail to reach a level to become market differentiator or…

netflix 工作原理_Netflix如何在屏幕后面工作?

netflix 工作原理Netflix has reported to have over 182 million subscribers worldwide in the first quarter of 2020 (Of course, these numbers don’t include freeloaders like me, who’s family or friends have been generous enough to share their account and pas…

B/S开发中浏览器的工具利器

B/S系统的前端浏览器性能和标准兼容方面是开发中的一个重要问题&#xff0c;把IE中使用的各种工具整理一下&#xff0c;对于开发中标准、规范检查等起到一个作用 主要是三个主流的浏览器和相关的插件 FireFox Firefox-latest.exe firebug-1.7.3-fx.xpi yslow-3.0.3-fx.xpi 安装…

你需要的git命令大全来了

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。虽然你可能用图形化工具…

uitest_您在Swift中的第一个UITest

uitest测试中 (TESTING) 什么是自动UITest&#xff1f; (What Is Automated UITest?) When we speak about testing, we usually think about unit testing. However, there is another kind of test that is extremely powerful and useful in the app world: UITests.当谈到…