前言
对于现在的前端工程,一个标准完整的项目,通常情况单元测试是非常必要的。但很多时候我们只是完成了项目而忽略了项目测试。我认为其中一个很大的原因是很多人对单元测试认知不够,因此我写了这边文章,一方面期望通过这篇文章让你对单元测试有一个初步认识。另一个方面希望通过代码示例,让你掌握写单元测试实践能力。
下面是一份抽样调查片段,抽样依据如下:
调查中的另一个有趣的见解是,在大型组织中单元测试更受欢迎。其中一个原因可能是,由于大型组织需要处理大规模的产品,以及频繁的功能迭代吧。这种持续的迭代方式,迫使他们进行自动化测试的投入。更具体地说,单元测试有助于增强产品的整体质量。
另外,报告显示超 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
npm 下载量
Jest 的下载量较大,一部分原因是因为 create-react-app 脚手架默认内置了 Jest, 而大部分 react 项目都是用它生成的。
从 github starts & issues 以及 npm 下载量角度来看,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 xxx
test('sum(1, 2) === 3', () => {
expect(sum(1, 2)).toBe(3);
});
})
可见无论是受欢迎度和写法上,Jest 都有很大的优势,因此推荐你使用开箱即用的 Jest
npm install --save-dev jest
首先,创建一个 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 将打印下面这个消息
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);
});
报错
为了能使用这些新特性,我们就需要使用 babel 把 ES6 转成 ES5 语法
安装依赖
npm install --save-dev @babel/core @babel/preset-env
根目录加入.babelrc
{ "presets": ["@babel/preset-env"] }
再次运行 npm run test ,问题解决
jest 运行时内部先执行( jest-babel ),检测是否安装 babel-core,然后取 .babelrc 中的配置运行测试之前结合 babel 先把测试用例代码转换一遍然后再进行测试
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
为了提高效率,可以通过加启动参数的方式让 jest 持续监听文件的修改,而不需要每次修改完再重新执行测试用例
改写 package.json
"scripts": { "test": "jest --watchAll" },
效果
单元测试覆盖率是一种软件测试的度量指标,指在所有功能代码中,完成了单元测试的代码所占的比例。有很多自动化测试框架工具可以提供这一统计数据,其中最基础的计算方式为:
单元测试覆盖率 = 被测代码行数 / 参测代码总行数 * 100%
加入 jest.config.js
文件
module.exports = {
// 是否显示覆盖率报告
collectCoverage: true,
// 告诉 jest 哪些文件需要经过单元测试测试
collectCoverageFrom: ['get.ts', 'sum.ts', 'src/utils/**/*'],
}
再次运行效果
参数解读
参数名 | 含义 | 说明 |
---|---|---|
% 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 等分支代码都执行了
},
},
上述阀值要求我们的测试用例足够充分,如果我们的用例没有足够充分,则下面的报错将会帮助你去完善
下面我们以 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');
});
});
关于断言方法有很多,这里仅摘出常用方法,如果你想了解更多,你可以去 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')
})
这里你可能看到这样一条报错
这是因为 @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"]
}
再次运行就不会出现报错了
.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 用来匹配浮点数(带小数点的相等)
以上就是文章全部内容,相信你阅读完这篇文章后,已经掌握了前端单元测试的基本知识,甚至可以按照文章教学步骤,现在就可以在你的项目中接入单元测试。同时在阅读过程中如果你有任何问题,或者有更好见解,更好的框架推荐,欢迎你在评论区留言!
也许在你阅读这篇文章之前,你本身就已掌握前端单元测试技能了,甚至已经是这个领域的大牛了,那么首先我感到非常荣幸,同时也诚恳的邀请你在评论区提出宝贵意见,我在这里提前说声谢谢!
最后感谢你在百忙之中抽出时间阅读这篇文章,送人玫瑰,手有余香,如果你觉得文章对你有所帮助,希望可以帮我点个赞!
浅谈前端单元测试 (https://juejin.cn/post/6844903624301084680)
Jest 官方文档 (https://www.jestjs.cn/docs/getting-started)
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Acas6QkCz06pAHojSNmhbg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。