您现在的位置是:网站首页> 编程资料编程资料
前端单元测试之UI测试功能性代码测试教程_DOM_
2023-05-24
286人已围观
简介 前端单元测试之UI测试功能性代码测试教程_DOM_
前言
《孤勇者》最近火爆的一塌糊涂,占领了小学生、甚至幼儿园,连我家2岁多的儿子尽然也会哼几句。虽然他以为这首歌是奥特曼的主题曲。
回到正题,现代前端项目的工程化、模板化日益壮大,各种类库框架层出不穷,整个行业俨然一副百花齐放百家争鸣的状态。而对于前端单元测试而言,在整个前端领域,实际上单元测试早已不算什么冷门话题,各种测试框架、库已经非常完善。
对于一个前端开发而言,实际上我们不太需要专注所有的这些测试框架、工具。我们只需要按照自己所需(团队、项目所需)选择适合自己的即可。
而怎么选型,实际也非常简单。在现在前端项目多数基于框架的大前提下,直接从官方文档上找单元测试的部分。官方必然会提供测试库或者推荐优秀的社区测试库。
从前端项目层面,无非就是UI的测试(简单的理解成DOM的测试) 、功能性代码测试。
UI测试:
很容易理解,前端的项目终究还是会展示在浏览器上,那么所谓的UI测试也就是测试我们写的代码和预期渲染的UI是否一致而已。
结合到我们现在实际项目,多数基于UI框架的情况(比如React),我们只需要跟随官方提供的UI测试途径即可,千万别去直接搜索XX前端单元测试框架之类的。每个框架都有自己的特性,或多或少都会涉及到UI的渲染,比如虚拟DOM、生命周期之类。所以像React官方文档提到的@testing-library/react(也有曾经使用过比较优秀的Enzyme),都会根据React框架特性提供相关的API例如:render、renderHook之类的。
本文对于UI的测试不进行详细介绍。
功能性代码测试:
简单的说就是一段封装的代码,无论是一个函数或者是一个类。而这段代码多数情况和UI无关,我们只需要要关注功能实现的本身,最简单的就是一个函数,输入什么参数,返回什么值。
功能性代码的测试将是本文讲述的重点,当然我们需要一个测试框架提供支持,本文讲述的Jest就是这种一个框架。
在介绍Jest之前,还是先聊一聊何谓单元测试,进行有效的单元测试。
让人闻风丧胆的单元测试
说起单元测试,尤其是对于前端行业的同学们来说,真的算的上是让人闻风丧胆,毕竟单测这个词听上去就是牛逼轰轰。
也经常有勇士会说,劳资React、antd一把梭哈,要个鸡儿的单元测试。
现实工作中,当你接收了别人的代码,大多数人可能会觉得在接手一座屎山,得想办法怎么在屎山上进行雕花,而且还得让山不崩塌。阿西吧,劳资还不如重构了。
stop!又双叒叕扯远了。
好了,别管测试同学嘴里的什么黑盒、白盒、性能、功能。对我们大多数人而言,单元测试,不就是对代码进行测试么。怎么测试呢?
代码测试代码
可能你有点明白了,那么问题来了,挖掘机哪家强?sorry,问题是如何用代码测试代码?
举个简单的例子,你写个了函数,判断请求是否成功。因为不可抗拒的因素,你们公司团队里面的后端同学,每个人的接口对于成功返回的定义不一样。有的是通过http 状态码200,有的根据返回值中的success是否为true,有的人返回了一个code字段,值可能为200也可能为"200"。
现实就是这么残酷,前端的小伙伴总是在写这样那样的兼容方案。
// utils.js /** * 判断请求是否成功,成功返回,失败提示失败信息 * @param {Object} response * @returns */ export const requestSuccess = response => { if ( response.data.success === true || response.data.code === 'success' || response.data.code === '1000' || response.data.code === 200 || response.data.code === '200' ) { return response; } message.error(response.data.message || '请求失败'); }; 对于这样一个常见的函数而言,我们需要如何对其进行单元测试呢?
所谓测试,无非就是在特定场景下(我们可以先不用管这个场景是什么),输入一些值,得到输出值,然后跟期望输出的值进行比对,看是否一致,如果一致则表明这一条测试用例通过。
看这个简单的例子,我们不用管下面的test和expect方法具体是干什么的。
// utils.test.js import { requestSuccess } from './utils.js' test('requestSuccess方法 请求正常测试用例', () => { const input = { data: { success: true, code: 200, message: '请求成功' } }; const expectOutput = input; const output = requestSuccess(input); expect(output).toEqual(expectOutput); }); 在上面的案例中,我们来逐步分解:
首先定义了一个输入:input。
然后将input作为参数,调用了requestSuccess方法,本次调用得到另一个返回值output。
最后就是判定,判定(也就是所谓的期望/断言)得到的输出值output等于期望的输出值expectOutput。
这是一段最基础的,正常输入值的单元测试代码,我们可以总结出大概的步骤:
- 1、定义输入值
- 2、将输入值带上,执行代码,得到输出值
- 3、对输出值进行断言
这个断言就是说你觉得这个输出值应该是什么,也断言这个输出值和你期望输出值匹配。当然,实际输出值和你的期望输出可能不匹配,那就是表明你的这条用例执行失败。失败的原因可能是你的源代码有问题,也可能是你单元测试的用例代码有问题。
OK,我们了解了最基础的单元测试。那么真正意义的单元测试应该怎么写呢?
无非就是写单元测试用例,定义各种输入值,判断和期望输出是否一致,然后进行分析修改。
再回归到上面的requestSuccess方法,上面的测试用例仅仅是验证了正常情况下,当然这种情况可能占大多数,但是单元测试一般就是为了兼容小部分的异常场景。
那么接下来,我们就来分析下一般意义上请求失败场景的测试用例:
// utils.test.js import { requestSuccess } from './utils'; test('requestSuccess方法 请求失败测试用例', () => { const input = { data: { success: false, message: '请求失败' } }; const output = requestSuccess(input); // 没有返回值,output为undefine expect(output).toBeUndefined(); }); 好了,到这里,有的同学说,请求正常、请求异常的情况都覆盖了,单元测试完成,可以提交测试,然后进行愉快的摸鱼了。
等等,事情没有那么简单。
测试同学急急忙忙来找你了,说你的程序又崩了,页面空白了。
你让测试同学给你复现了,一步一步debug。原来发现,调用你requestSuccess方法的response参数,尽然为一个空对象: {} 。
你可能会直呼好家伙,后端不讲武德啊(当然可能性很多,可能并不是后端一个人的锅),因为不可抗拒因素,你又得去改代码,一边改一边骂。
改完之后的源码如下,然后你又得意的告诉测试同学已经改完,没有问题了。
export const requestSuccess = response => { if ( response.data?.success === true || response.data?.code === 'success' || response.data?.code === '1000' || response.data?.code === 200 || response.data?.code === '200' ) { return response; } message.error(response.data.message || '请求失败'); }; 结果不一会,测试同学说,你的程序又崩了,页面空白了。
你慌了,自言自语的说道,没问题啊,劳资都写了兼容了,让测试同学给你复现了,一步一步debug。原来发现,调用你requestSuccess方法的response参数,尽然为undefined。你破口大骂,告诉测试是后端的锅,是另一个前端瞎鸡儿调用,和你无关。掰扯了一段时间,你又改了下你的代码:
// 当然下面的代码还是可以继续优化 export const requestSuccess = response => { if ( response?.data?.success === true || response?.data?.code === 'success' || response?.data?.code === '1000' || response?.data?.code === 200 || response?.data?.code === '200' ) { return response; } message.error(response.data.message || '请求失败'); }; 再回到单元测试的正题上,上面的那些异常情况,在实际项目运行中比比皆是。而除了配合测试同学发现bug然后修改之外,我们在单元测试的时候即可发现,并优化自己的代码。
例如requestSuccess针对这个方法而言,我们先不用去管实际调用时候什么请求成功、请求失败,只去针对这个方法本身,调用requestSuccess方法的参数可能性是非常多的,各种类型的,所以我们可以以每一种类型的输入值作为一条测试用例。
// utils.test.js import { requestSuccess } from './utils'; // 这个describe可以不用纠结,理解成几份用例的集合,只是统一为异常输入的描述 describe('requestSuccess方法异常输入测试用例', () => { test('response为空对象测试', () => { const input = {}; const output = requestSuccess(input); expect(output).toBeUndefined(); }); test('response为undefined测试', () => { const output = requestSuccess(); expect(output).toBeUndefined(); }); test('response为Number类型测试', () => { const output = requestSuccess(123); expect(output).toBeUndefined(); }); }); 在写了这么多的异常输入的测试用例之后,你会发现你一开始写的requestSuccess不够强大,导致单元测试用例执行失败,所以你可以一遍又一遍的修改你的源码,直至测试用例都通过。
总结: 如何进行有效的单元测试,最简单的做法就是考虑各种异常/边界输入值,编写相应的测试用例,通过单元测试的执行,优化你的代码。
当然做好单元测试,并不仅仅只是说考虑各种异常输入即可,实际还会涉及到开发时候 的考虑(比如常说的测试驱动开发之类的)以及非常多的实现细节,这个可能就需要你慢慢的理解了。
Jest介绍
Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
It works with projects using: Babel, TypeScript, Node, React, Angular, Vue and more!
官方的介绍就是上面2段话,就是说jest是一个让人愉悦的js测试框架,专注于简单性。可以配合babel、ts、node、react、angular、vue等其他库 一起使用。
我们前文提及的什么describe、test、expect方法等等在Jest中都有相应的api。
一、基础教程
安装
可以使用yarn或者npm进行安装
yarn add jest -D | npm i jest -D
源码开发
这里举了一个简单的例子,实际组件开发需要使用ts以及其他UI测试框架。
例如开发一个基础方法,返回2个参数的和。文件名为sum.ts
// sum.js function sum(a, b) { return a + b; } export default sum; 测试用例编写
首先我们根据上面的目标文件(sum.js)创建一个测试用例文件-- sum.test.js, 测试用例文件名称统一为*.test.js(后缀根据实际场景区分为.js或者.ts或者.tsx)
// sum.test.js import sum from './sum'; test('adds 1 + 2 to equal 3', () => { expect(sum(1, 2)).toBe(3); }); 开始测试
添加下面的部分到你的package.json中
{ "scripts": { "test": "jest" } } 最后,执行yarn testornpm run test命令,Jest将会打印如下信息:
PASS ./sum.test.js
✓ adds 1 + 2 to equal 3 (5ms)
就这样,基于jest的一个基础单元测试流程走好了,Jest的单元测试核心就是在test方法的第二个参数里面,expect方法返回一个期望对象,通过匹配器(例如toBe)进行断言,期望是否和你预期的一致,和预期一致则单元测试通过,不一致则测试无法通过,需要排除问题然后继续进行单元测试。
更多的配置以及命令行参数请参考官方文档下面开始讲解一些核心API。
二、核心API
全局方法
在你的测试文件中,Jest将下面这些方法和对象放置全局环境,所以你无需再显式的去require或者import。当然,如果你更喜欢显式的import,也可以使用例如import { describe, expect, it } from '@jest/globals'的方式。
test(name, f
相关内容
- JavaScript给数组添加元素的6个方法_javascript技巧_
- Vue组件通信深入分析_vue.js_
- Vue NextTick介绍与使用原理_vue.js_
- defineProperty和Proxy基础功能及性能对比_vue.js_
- React中井字棋游戏的实现示例_React_
- Vue--Router动态路由的用法示例详解_vue.js_
- Vue双向数据绑定与响应式原理深入探究_vue.js_
- vue混入mixin的介绍及理解_vue.js_
- 3分钟精通高阶前端随手写TS插件_JavaScript_
- JavaScript数组扁平转树形结构数据(Tree)的实现_javascript技巧_
