浅谈如何进行有效的前端测试_第1页
浅谈如何进行有效的前端测试_第2页
浅谈如何进行有效的前端测试_第3页
浅谈如何进行有效的前端测试_第4页
浅谈如何进行有效的前端测试_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第第页浅谈如何进行有效的前端测试浅谈如何进行有效的前端测试

发表于:2023-07-03来源:未知:未知点击数:标签:前端测试

前端测试或许被好多人误解,也许大家更加倾向于编写面向后端的测试,逻辑性强,测试方便等聊到这导致了好多前端从来不写测试(测试全靠手点~~~)其实没必要达到测试驱

前端(测试)或许被好多人误解,也许大家更加倾向于编写面向后端的(测试),逻辑性强,测试方便等

聊到这导致了好多前端从来不写测试(测试全靠手点~~~)

其实没必要达到测试驱动(开发)的程度,只要写完代码可以补测试,并且补出高效的测试,前端或许真的不需要手点

大前端时代不谈环境不成方圆,本文从下面几个环境一一分析下如何(敏捷测试)

node环境vue环境nuxt服务端渲染环境react环境next服务端渲染环境angular环境理解测试前需要补充下(单元测试)(unit)和端到端测试(e2e)的概念,这里不赘述

node环境

推荐测试框架jest

jest是FB的杰作之一,方便各种场景的js代码测试,这里选择jest是因为确实方便

使用方法及配置信息可以去官方文档

配置的注意事项

{testEnvironment:node//如不声明默认浏览器环境}针对node只聊一下单元测试,e2e测试比较少见

当决定写一个npm模块时,代码完成后必不可少的就是单元测试,单元测试需要注意的问题比较琐碎

mock

当引入三方库时,不得不mock数据,因为单元测试更多讲求的是局部测试,不要受外界三方引入包的影响

例如:

const{readFileSync}=require(fs)constgetFile=()={try{consttext=readFileSync(text.txt,utf8)}catch(err){thrownewError(err)}console.log(text)}module.exports=getFile这时我们并不需要关心text.txt是否真的存在,也不需要关系text的内容具体是什么,我们的关注点应该在于读取文件错误时能否及时抛出异常,以及console.log()是否如预期执行

对应到测试

constgetFile=require(./getFile)describe(readFile,()={constmocks={fs:{readFileSync:jest.fn()},other:{text:Testtext}}beforeAll(()={jest.mock(fs,()=mocks.fs)})test(readfilesuccessrunconsole.log,()={mocks.fs.readFileSync.mockImplementation(()=this.mocks.other.text)getFile()expect(console.log).toBeCalled()})})上面代码简单的实现了一个读取文件是否成功的测试,先别急着纠错,这段测试本身是错的,下面慢慢分析

我们在最开始创建了一个mocks对象,用来模拟数据,由于readFileSync方法可能存在多种返回结果(成功或报错),所以暂时用jest.fn()模拟

other里面则是放一些固定的测试数据(不会随着测试过程而改变)

beforeAll钩子里面执行我们的mock,把require进来的fs模块拦截调,也是本(测试(用例))中的关键步骤

在第一个test里面我们改写mocks.fs.readFileSync的返回形式,这里使用的mockImplementation是直接模拟了一个执行函数,当然也可以模拟返回值,具体可以到jest官网

expect用来断言我们的console.log方法执行了

解释了这么多测试新手们应该也都看的明白了,下面聊一下错在哪,怎么改进

mockImplementation最好替换为mockReturnValueOnce,注意这里出现了Once结尾,也就是仅模拟一次返回值,mockImplementation最好使用在复杂场景,所谓的复杂就是我们手动实现一个readFileSync方法使得测试达到我们预期的目的,在这个简单的场景里面我们只需要模拟返回值就好expect(console.log)这里会报错,因为jest断言的内容只能是mockfunction或spy,这里console是全局对象global上的方法,我们没有require将其引入,所以jest.mock显然处理上有些吃力,这时候spy就派上用场了,beforeAll钩子里直接执行jest.spyOn(global.console,log),接下来我们就能监听到console.log的执行了expect(global.console.log)断言的目的是测试console.log的执行,这是不严谨的测试,我们需要使用toBeCalledWith来代替toBeCalled,不仅要测试执行了,而且要测试参数正确,简单修改为expect(global.console.log).toBeCalledWith(this.mocks.other.text)下面补一下readfile失败的测试

test(readfilefailthrowerror,()={mocks.fs.readFileSync.mockImplementationOnce(()={thrownewError(readFileerror)})expect(getFile()).toThrow()expect(global.console.log).not.toBeCalled()})读取文件失败的测试就好理解的多,注意的就是对一个jest.fn()多次进行修改会导致测试用例之间的相互影响,这里尽量使用Once结尾方法,复杂场景可以如下

beforeEach(()={mocks.fs.readFileSync.mockReset()})每次执行test前先清除mock,避免多个测试用例之间复杂化mock导致错误

小结:单元测试中的mock是个测试思路,我们无需关心外部文件和依赖是什么,只要能模拟出正确的情况程序是否按规则执行,错误的情况程序是否有异常处理,逻辑是否正确等。这样就能排除外界干扰,使得我们测试的当前一小部分是可靠的,稳定的即可。

引用外部文件

单拿出一个小结说下require的问题,node9之前不支持es6的import,这里也不详细说明了。

require本身并不复杂,但是如果搞不清楚执行时机,那么测试将无法进行,来一个例子

constenv=process.env.NODE_ENVmodule.export=()=env测试如下

constgetEnv=require(./getEnv)describe(env,()={test(envwillbedev,()={process.env.NODE_ENV=devexpect(getEnv()).toBe(dev)})test(envwillbepord,()={process.env.NODE_ENV=pordexpect(getEnv()).toBe(pord)})})十分简单的测试,抛开了mock的流程,这里会报测试未通过,原因是require同时env已经被赋值为undefined,我们再试着改变NODE_ENV环境变量时,程序不会再次执行,当然了,处理起来也十分简单

letgetEnvtest(envwillbedev,()={process.env.NODE_ENV=devgetEnv=require(./getEnv)expect(getEnv()).toBe(dev)})test(envwillbepord,()={process.env.NODE_ENV=pordgetEnv=require(./getEnv)expect(getEnv()).toBe(pord)})顺带说了一下,希望大家不要在这种低级错误上浪费时间

其实引用外部文件还有些场景会对测试带来困惑,比如动态路径,场景如下

constpackageFile=`${process.cwd()}/package.json`constpackage=require(packageFile)读取当前路径下的package.json,当测试真正跑到这段代码时会到当前目录下找package.json,这里尽量mock掉package.json为我们自己的模拟数据,但是jest不支持动态路径的mock,试着这样写jest.mock(${process.cwd()}/package.json,()=mockFile)会报错,所以尽量使用可以mock的方案,保证单元测试可以顺利进行,修改如下

constpath=require(path)constfilePath=path.join(process.cwd(),package.json)这样就可以mock,path了,和上面mock章节,大致思想都差不多

覆盖率

单元测试覆盖率不达标等于白测,测试过程尽量覆盖所有判断条件,而不是全部通过了就不管了,在进一阶说,100%的测试覆盖率并不证明一定覆盖到位了,因为顺带执行的代码也会算进覆盖率,例如

module.export=(list)=list.map(({id})=id)我们先不考虑这个list类型是不是数组,只是简单的例子,避免过度设计带来复杂化,我们测试可以这样

constgetId=require(./getId)constmocks={list:[{id:1,name:vue},{id:2,name:react}]}test(returnid,()={expect(getId(mocks.list)).toEqual([1,2])})直到有一天代码变成了module.export=(list)=[1,2]

这时候测试还能通过,并且覆盖率100%,的确不会有人蠢到把代码改成这样,只是一个例子,实际上逻辑会比这个复杂的多

那就聊一聊解决方案

mock数据的随机化,每次测试生成随机的list进行测试,现有库mockjs强关联测试,证明map方法的确执行了,并且参数正确,先spyspyOn(Atotype,map)然后断言聊了一圈从覆盖率聊到了测试健壮性的问题,可以思考下写过的测试是否真的满足解释或修改任何一行代码都能引起测试的pass报错

关于node就聊这么多,其实下文主要思想都一样,更多的是介绍些简单可行的方案,以及可能会踩坑的地方

vue环境

在vue使用场景下,无非就是组件库和业务逻辑,组件库偏向于unit测试,业务逻辑偏向于e2e测试,当然两者并不冲突

unit测试

推荐神器:vue-test-utils

README给了多个测试库配置的例子,这里还是推荐使用jest,给个例子

exportdefault{props:[value],data(){return{currentValue:0}},watch:{value(val){this.currentValue=val}}}测试如下

import{mount}from@vue/test-utilsimportTestfrom./Test.vuetest(propsvalue,()={constoptions={propsData:{value:3}}constwrapper=mount(Test)expect(wrapper.vm.currentValue).toBe(3)})十分简单的例子,亮点在测试文件的wrapper上,通过mount方法创建了一个组件实例,创建过程中允许加入一些配置信息,甚至是mock组件中的method方法

vue单元测试的范围仅限于数据流动是否正确,逻辑渲染是否正确(v-ifv-showv-for),style和class是否正确,我们并不需要关系这个组件在浏览器渲染中的位置,也不需要关系对其它组件会造成什么影响,只要保证组件本身正确即可,前面说的断言,vue-test-utils都能提供对应的方案,总体上节约很多测试成本

e2e测试

也是推荐尤大基于最新脚手架的@vue/cli-plugin-e2e-nightwatch

e2e测试的重点在于判断真实DOM是否满足预期要求,甚至很少出现mock场景,不可或缺的是一个浏览器运行环境,具体细节不赘述,可以看官方文档。

nuxt服务端渲染环境

nuxt官方推荐ava,顺势带出ava的方案

unit测试

麻烦在配置上面,先给出需要安装的依赖

@vue/test-utils,ava,browser-env,require-extension-hooks,require-extension-hooks-babel,require-extension-hooks-vue,sinon在package.json里加几行ava配置

ava:{require:[./tests/helpers/setup.js]}下面来写./tests/helpers/setup.js

consthooks=require(require-extension-hooks)//Setupbrowserenvironmentrequire(browser-env)()//Setupvuefilestobeprocessedby`require-extension-hooks-vue`hooks(vue).plugin(vue).push()//Setupvueandjsfilestobeprocessedby`require-extension-hooks-babel`hooks([vue,js]).plugin(babel).push()上面的代码唯独没看到sinon这个库,说到ava是没有mock功能的,这就给单元测试的mock带来巨大困难,不过我们可以通过引入sinon来解决mock数据的问题,在mock方面上sinon做的比jest还要优秀,支持沙箱模式,不影响外部数据

给个简单点的例子

templateel-cardv-for=itemintopicList:key=item.iddivclass=card-contentspanclass=link@click=toMember(item.member.username){{item.member.username}}/span/div/el-card/templatescriptexportdefault{props:{topicList:{type:Array,required:true}},methods:{toMember(name){this.$router.push(`/member/${name}`)}}}/script对应的测试代码如下

import{shallowMount}from@vue/test-utilsimporttestfromavaimportsinonfromsinontest(methods:toMember,t={const{topicList}=t.contextconst$router={push:()={}}constspy=sinon.spy($router,push)constwrapper=shallowMount(TopicListChalk,{propsData:{topicList},mocks:{$router}})topicList.forEach((item,index)={consttoMemberText=wrapper.findAll(.card-content).at(index).find(.link)toMemberText.trigger(click)t.true(spy.withArgs(`/member/${item.member.username}`).calledOnce)})})这里直接将$routermock掉,并且使用sinon.spy监听执行,至于this.$router.push后浏览器有没有跳转并不是单元测试需要关心的,这里的写法也比较特别,test方法在回调里默认参数为t,对应的方法都挂载在t对象上,上下文可通过t.context传递

nuxt单元测试相关就聊这么多

e2e测试

这里有个歧义点,nuxt官网只给出了e2e的测试案例end-to-end-testing

当使用默认脚手架构建的项目,也就是没有server端入口文件的项目,这个方案确实可行

但是涉及到其它框架(express|koa)的时候就显得不够用了,很有可能在自定义server入口是加入了大量中间件,这对于官网给出的例子是个巨大考验,不可能在每个测试文件里实现一遍newNuxt,所以需要更高层的封装,也就是忽略server启动流程的差异性,直接在浏览器中抓取页面

推荐:nuxt-jest-puppeteer

react环境

unit测试

这一波没得可选,jest完胜,人家官网就有React,RN的支持文档

文档的案例也是十分全面,没得讲,不赘述

e2e测试

其实上面讲了两个e2e的方案选择,大同小异,需要一个能在node

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论