前端测试体系和最佳实践

2023-06-03 0 726

序言

我曾经在三四个工程项目里都几近完备参与过补上后端试验的工作,也搜集到不同工程项目的同僚许多关于后端试验的疑惑和关键点,这其中大部分都很相近,我也感到恐惧,在这首诗里,就要针对他们和他们常遇到的关键点撷取许多他们的实战经验,如果你也有如下表所示相近的所苦,那希望这首诗能对你很多协助~

常用问题(名列不分先后):

后端试验感觉写出来很复杂,所花许多天数,即使经常是销售业务标识符天数的十倍后端试验是不是TDD?试验许多服务器端UI命令行时,不光难演示与之的可视化很多东西不晓得是不是mock,比如天数,应用程序函数调用(window.location,local storage)等试验里准备数据的标识符不光长,真正的试验标识符很靠后,要翻很久,不容易功能定位跑试验时能冒出许多Error或Warn Log,好像不影响试验透过,修出来也很花天数,修么?

在撷取问题的相关实战经验之前,他们先来剖析呵呵后端试验管理体系~

后端试验管理体系

后端试验的必要性

这其实跟所有试验的必要性是一样的,他们有这么多的关键点也是因为晓得全面覆盖全面的试验能对标识符质量更有保证,让他们更有信心地去解构标识符,也能协助他们更方便快捷地了解现有的机能技术细节,即使是许多顽固的边界线情况。而且在大家合作联合开发工程项目标识符的过程中,试验能协助他们更早地Cogl,减少天数成本,提高交货工作效率。

后端试验认识论(TDD vs. BDD)

这两个常用的试验认识论在这儿单纯介绍呵呵,就不图文并茂展开了

TDD – (Test-Driven Development 试验驱动力联合开发)单纯蔡伯介是先根据需求写试验用例,接着同时实现标识符,透过后再接着写出一个试验和同时实现,循环式直到全部机能和解构完成。核心思想是透过试验来推动整个联合开发的进行。

BDD – (Behavior Driven Development 犯罪行为驱动力联合开发) 只不过能看作是TDD的一个组成部分。单纯蔡伯介是先从外部表述销售业务犯罪行为,也是试验用例,接着由外入内的同时实现这些犯罪行为,最后得到的试验用例也是相应销售业务犯罪行为的环评标准。

后端试验的多层

在这儿借呵呵后端薄罗藓Kent C. Dodds的奖座多层法来带出常用的进行分类:

前端测试体系和最佳实践

(图片出处:https://kentcdodds.com/blog/static-vs-unit-vs-integration-vs-e2e-tests)

端到端试验 End to End Test

端到端试验一般会运行在完备的应用系统上(包括后端和后端),包含用户完备的使用场景,比如打开应用程序,从注册或登录开始,在页面内导航,完成系统提供的机能,最后登出。

有时,他们也会在这儿引入可视化用户界面试验,即一种透过像素级比较屏幕截屏来验证页面显示是否正确的试验。目的是确保界面在不同设备、应用程序、分辨率和操作系统下与预期的样式一致。能设置一定的偏差容忍值。

这一层的试验成本较高,所以通常重心会放在确保主流程的机能正常上。

常用工具:Cypress、Playwright、Puppeteer、TestCafe、Nightwatch (下载量对比)

集成试验 Integration Test

集成试验主要是试验当单元模块组合到一起之后是否机能正常。

在不同的试验上下文下可能有不同的表述,在后端试验这儿通常指试验集成多个单元组件到一起的组件。

单元试验 Unit Test

单元试验是对没有依赖或依赖都被mock掉了的试验单元的试验。在后端标识符里,它可能是:

没有依赖或依赖都被mock掉了的单元组件机能标识符如Utils/Helpers等公共方法集合的试验辅助组件机能如React Hook / Selector等公共方法的试验

静态标识符试验 Static Test

主要是指利用许多标识符规范工具(Lint Tool)来及时捕获标识符中潜在的语句错误,统一标识符格式等。这儿就不展开了。

常用工具和课堂教学有:

Eslint + Prettier 标识符规范和样式统一

husky + lint-staged (gitHooks工具)能自动在commit和push之前进行标识符扫描,阻止不规范标识符进入标识符库,也能设置在push之前跑一遍后端试验

后端试验策略

还是这张图,我标记了呵呵:

前端测试体系和最佳实践
越往上成本越高越往上得到反馈的速度越慢但越往上,越贴近最终用户的犯罪行为,越能发现真实的问题,能给到的信心就更多

在奖座的形状上每一层占的面积代表了应该投入的重心比例。

这儿集成试验的比重比单元试验大是因为集成试验能在成本很高的e2e试验和离最终用户犯罪行为较远的单元试验之间取的一个平衡,它能写的很接近最终用户的犯罪行为,成本又相对的没那么高,属于性价比很高的一部分。

所以集成试验有许多原则

能根据每个页面的复杂程度决定是只有一个全页面的集成试验还是能划分成几大块分别有集成试验,但一旦作为集成试验,就要尽可能的少mock依赖,尽量的渲染全子组件尽量试验用户的所见和可视化,而不是背后的同时实现,否则就会远离最终用户行为,降低信心值,而且随着标识符的解构,试验也需要频繁的修改。比如Enzyme能把component里的方法、props、state等都提供出来单独试验,但这儿的试验并不贴近真实用户的可视化,很容易就会因为解构而破坏试验,更好的方法是真的去试验当props和state变化后页面的变动,或可视化的变化准备的试验数据尽量丰富且贴近真实数据(用户敏感信息要替换掉),越贴近真实的数据越能全面覆盖到更多真正的问题对于核心的销售业务犯罪行为,要重点试验

对于单元试验来说:

UI组件类的试验:因为有了集成试验的全面覆盖,能单纯的试验呵呵不同props的渲染,如果有许多集成试验全面覆盖不到的特殊数据引发的可视化犯罪行为,能试验呵呵非UI组件类的试验:通常会全面覆盖许多复杂的销售业务逻辑,需要全面的试验呵呵不同的组成部分条件

后端试验工具的进行分类

试验启动工具 (Test Launchers)

试验启动工具负责将试验运行在Node.js或应用程序环境。形式可能是CLI或UI,并结合一定的配置。常用工具有:Jest / Karma / Jasmine / Cypress / TestCafe 等

试验结构工具 (Structure Providers)

试验结构工具提供许多方法和结构将试验组织的更好,拥有更好的可读性和可扩展性。如今,试验结构通常以BDD形式来组织。试验结构如下表所示方Jest例子:

// Jest test structure describe(calculator, () => { // 第一层级: 标明试验的模块名称 beforeEach(() => { // 每个试验之前都会跑,能统一添加许多mock等 }) afterEach(() => { // 每个试验之后都会跑,能统一添加许多清理机能等 }) describe(add, () => { // 第二层级: 标明试验的模块机能分组 test(should add two numbers, () =>{// 实际的描述销售业务需求的试验 … }) }) })

常用工具有:Jest / Mocha / Cucumber / Jasmine / Cypress / TestCafe 等

断言库 (Assertion Functions)

断言库会提供一系列的方法来协助验证试验的结果是否符合预期。如下表所示方的例子:

// Jest expect (popular) expect(foo).toEqual(bar) expect(foo).not.toBeNull() // Chai expect expect(foo).to.equal(bar) expect(foo).to.not.be.null

常用工具有:Jest / Chai / Assert / TestCafe 等

Mock工具

有的时候他们在试验的时候需要隔离许多标识符,演示许多返回值,或监控许多犯罪行为的调用次数和参数,比如网络请求的返回值,许多应用程序提供的机能,天数计时等,Mock工具会协助他们更容易的去完成这些机能。

常用工具有:Sinon / Jest (spyOn, mock, useFakeTimers…) 等

快照试验工具 (Snapshot Comparison)

快照试验对于UI组件的渲染试验十分有效。原理是第一次运行时生成一张快照文件,需要联合开发人员确认快照的正确性,之后每一次运行试验都会生成一张快照并与之前的快照做比较,如果不匹配,则试验失败。这时如果新的快照确实是更新标识符后的正确内容,则能更新之前保存的快照。(这儿的快照通常都是框架渲染器生成的序列化后的字符串,而不是真实的图片,这样的试验工作效率比较高)

这儿能参考Jest官方的用例。

常用工具有:Jest / Ava / Cypress

试验全面覆盖率工具(Test Coverage)

试验全面覆盖率工具能产出试验全面覆盖率报告,通常会包含行、组成部分、函数、语句等各个维度的代码全面覆盖率,还能生成可视化的html报告来可视化标识符全面覆盖率。

如以下的Jest内置的标识符全面覆盖率报告:

前端测试体系和最佳实践

(图片出处:https://jestjs.io/)

常用工具有:Jest内置 / Istanbul

E2E 试验工具(End to End Test)

上面在试验多层里介绍过的。

可视化用户界面试验(Visual Regression)

也在上面的试验多层里介绍过。通常会和e2e试验工具组合在一起使用,一般主流的e2e试验工具也会有对应的库去进行可视化用户界面试验。

后端框架专属试验库

不同的后端框架还会有许多自带的或推荐的试验库,比如:

React: React官方的Test Utils / Testing Library – React(推荐) / Enzyme (基于上面的试验策略,更推荐React Testing Library,Enzyme暴露了太多内部元素用来试验,虽然一时方便快捷,但远离了用户犯罪行为,之后带来的修改频率也比较高,性价比低)

Vue: Vue官方的Test Utils / Testing Library – Vue

Angular: Angular内置的试验框架(Jasmine) / Testing Library – Angular

后端试验框架

基于上面的进行分类,他们可能发现几乎哪哪都有Jest,这类大而全的后端试验工具他们也能称为后端试验框架。

常用的有:

Jest:大力推荐,几乎有测试需要的所有工具,社区活跃,网上资源丰富,也是React官方推荐的试验框架

Mocha:虽然也机能丰富,但没有断言库、试验全面覆盖率工具和Mock工具,需要和其他服务器端库配合使用

Jasmine:比较老派的工具,机能也没有Jest丰富,下载率逐年下降

最后附上一张stateOfJS网站2021年的试验库满意度图表供他们参考

前端测试体系和最佳实践

(图片出处:https://2021.stateofjs.com/en-US/libraries/testing/)

后端试验的常用问题

终于回到最开始的问题了,撷取呵呵我的实战经验和通常的解决办法:

后端试验感觉写出来很复杂,所花许多天数,即使经常是销售业务标识符天数的十倍

这个问题能分成三部分来下手:

优化试验策略

能根据刚才的试验策略部分,结合他们工程项目的实际情况,调整呵呵在不同的试验层分配的重心,定呵呵他们工程项目每个层级的试验粒度,这样才能在保证交货的前提下达到试验信心值收益的最大化

提升写试验工作效率

抽取公共的部分,使具体的试验文件简洁准备数据的fixture库,能轻松的生成想要的store数据或请求返回数据公共的render方法,能支持自表述store, stub子组件, mock框架全局方法等公共的服务器端UI组件可视化方法,能轻松的触发服务器端命令行的事件,不用再关心同时实现技术细节公共的api mock方法,能在试验文件里不用关心api技术细节,轻松mock统一试验规范,有优化及时解构所有试验,这样他们能放心的参考已有试验,不会有多种写法影响可读性

提升运行试验的工作效率

并行跑试验试验里常用如下表所示方法使待测的异步请求返回,通常也会给setTimeout一个等待天数,大部分的情况0就能达到目的了,除非是逻辑真的要等待一定的天数,如果默认值都设置的比较大,每个试验都会耽误许多天数,加出来对试验运行性能的影响是很大的// testUtils.js export constflushPromises =(interval = 0) => { return new Promise((resolve) => { setTimeout(resolve, interval); }); }; // example.test.js test(should show …, async () => { //render component await flushPromises(); //verify component });

后端试验是不是TDD

通常问这个问题背后隐藏的问题是后端很难先写试验,再写同时实现。确实我也有同感,如果是许多util/helper方法是能很容易的遵循TDD的步骤的,但当涉及页面结构和样式的时候,很难在写试验的时候就想清楚页面到底有哪些具体的元素,用到哪些需要mock的模块。

所以在试验UI组件时,我通常会使用BDD的方式,具体步骤是:

建立组件文件,渲染返回空建立试验文件,先写一个snapshot试验,试验会透过,生成一个snapshot文件再根据这个页面mockup上已知的可视化写好test case,通常这个时候不太容易写同时实现,就先把试验用例都写好,test先skip出来,eslint能设置成skip的test用warn来展示,这样之后方便快捷补全// Jest describe(todo component, () => { test(should show todo list, () => { //Snapshot test const tree = renderer.create(<Todo />).toJSON(); expect(tree).toMatchSnapshot(); }) test.skip(should add todo when click add and input todo content, () =>{ }) test.skip(should remove todo when click delete icon of todo item, () =>{ })随着页面解构,可能会给组件添加props,这时也需要给不同的props添加snapshot试验或可视化试验最后能根据试验跑完的试验全面覆盖率报告看看是否全面覆盖全面了,防止有遗漏

当然随着后端标识符写的越来越熟练,为了提升工作效率,有时能简化步骤,等一个小机能的组件都解构完了,样式调好了,所有的子组件都抽完了,再根据每个组件的props和可视化的点批量加试验,最后用试验全面覆盖率来验证是否都全面覆盖到了,保证他们新写的组件都尽可能是100%的全面覆盖率。

试验许多服务器端UI命令行时,不光难演示与之的可视化

这个是我也很头疼的问题,有的时候许多服务器端组件因为要同时实现许多复杂的效果,会使用不一样的方式去监听事件。

比如他们有一个Vue工程项目上用到了element-ui的select组件,这个组件能透过:remote-method 属性开启异步发请求加载选项的机能,试验里想演示异步拿到选项后并选择某选项,就需要想办法触发它的@change 事件,通常一条await ***fireEvent***.update(input, S); 就搞定了,但这个是不是都不生效,仔细的查看它的同时实现才发现需要这么一串操作才能触发到@change 事件。

constinput = getByPlaceholderText(Please input to search); await fireEvent.click(input); awaitfireEvent.keyUp(input, { key:A, code: KeyA }); await fireEvent.update(input, A); await flushPromises(500); // 这个方法上面有介绍,的作用是让异步的标识符返回结果,并且等待500ms,因为源码有500ms的等待,这儿就也需要等待 await fireEvent.click(getByText(Apple));

这儿我总结的实战经验是:

如果发现常用的可视化方法不能生效,需要去研究服务器端组件的源码更重要的是如果他们研究出来了方法,及时的把相关标识符抽到一个公共的util文件里,这样之后就不会有人也花费许多天数在上面了,确实经常遇到他们重复卡在相同的服务器端组件可视化问题上而不晓得已经有标识符解决了的场景

很多东西不晓得是不是mock,比如天数,应用程序函数调用(window.location,local storage)等

这个能结合使用的试验工具去搜索,一般都会有许多现成的解决方案,在这儿举两个例子:

Mocknavigator.userAgent

// jest.setup.js Object.defineProperty( global.navigator, userAgent, ((value) => ({ get() { return value; }, set(v) { value = v; }, }))(global.navigator[userAgent]), ); // example.test.js test(should show popup inSafari, () => { global.navigator.userAgent = user agent of Safari …;// render and verify something });

Mock window.open

//jest.setup.js Object.defineProperty( window, open, ((value) => ({ get() { return value; }, set(v) { value = v; }, }))(window.open), ); // example.test.js test(should …, () => {window.open = jest.fn(); // render something expect(window.open).toBeCalledWith(xxx, _blank); });

试验里准备数据,mock依赖的标识符不光长,真正的试验标识符很靠后,要翻很久,不容易功能定位

上面有介绍,能将公共的部分抽取出去,又能减少标识符重复,又能提升写试验的工作效率

比如准备数据的部分能抽成公共的fixture文件,提供方法生成默认的数据,也能透过参数去全面覆盖修改部分数据,达到定制化的目的

export const generateUser = (user = {}) => { return { id: 1, firstName: San, lastName: Zhang, email: [email protected], …user, }; };

跑试验时能冒出许多Error或Warn Log,好像不影响试验透过,修出来也很花天数,修么?

试验里的报错通常都很有价值,需要重视。这儿面的错误有可能是:

后端框架相关的,比如被测的组件有写的或调用的不合理的情况,这种有的时候不仅是试验调用组件方式的问题,有可能销售业务标识符也写的有问题;或者是试验语句写的不合理,如React的 not wrapped in act(…)试验运行相关的,比如很多请求没有mock,试验里一直等不到返回值而timeout了,但又不是主测的销售业务,所以试验还是会透过,之前有遇到许多次试验并行跑时能互相影响,随机挂,如果log里有类似这种timeout的内容,很有可能是原因,mock好了所有的请求后问题就解决了

虽然有的时候也会有许多由于服务器端库的原因引起的无法修复又没有影响的log,能忽略,但试验里大部分警告Log只不过都是能修复的,即使在修复后可能得到意想不到的受益,比如发现真正销售业务标识符的问题,试验不再随机挂了,试验运行性能提升了等等。

总结

对于后端试验,我觉得重心不是机械的去追求试验全面覆盖率,而是尽可能的在成本和信心值中间找到一个平衡,应用一些好的课堂教学去降低写试验的成本,提升写试验带来的回报,让他们对于工程项目质量越来越有信心。

文/Thoughtworks 张霄翀

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务