作为一个前端开发到底要不要写测试?来看看《前端要写单元测试?不存在的!那e2e呢?》

熊丸子

故事背景

Up所在的开发团队,由于测试人员(以下简称QA)的资源匮乏,较难保出品质量,穷则思变,近年来Up尝试和实践了前端的各类测试方法,今天写出来与大家分享,讨论

前端测试的种类

简单过一下目前前端测试的种类,这不是重点,了解的同学可以跳过不看

网上介绍各类前端测试的文章不少,光SF就有不少好文,我大致归类了一下,主要分为2类

 1. 前端单元测试  (Unit Test以下简称 - UT)
 2. 前端e2e测试

第1点单元测试,大家应该不陌生,就算你没写过前端的UT,那你肯定也听过什么mocha,jasmine,jest这类测试框架,UT的意义在于比较细粒度的去测试我们业务代码中写的function,测试function里提供的method是否可靠。

就好比造房子的时候我们对每块砖的密度,重量,长宽高是否符合规范等数据进行测试,以确保每块砖都是ok的,不会出现空心砖等情况,从而保证最后造出来的房子没有安全隐患

第2点e2e测试
e2e测试就是端对端测试,简而言之,就是利用一些工具库提供的API使用代码来模拟终端用户在UI界面上的操作,比如输入,点击等等。目前常用的工具有,selenium, puppeteer,phantom,protractor(angular), Nightwatch(Vue)等等

重点来了!我的团队到底需不要前端开发写测试?

根据上一段的内容,我们分为2块来讨论

需不需要写“单元测试”?

上一段举了一个砖头与房子的例子来简单阐述了单元测试的重要性,但是这也恰恰说明了另外一个问题,也就是越是基础的,底层的代码越是需要进行完备的单元测试,以保证它是可靠的,从而保证依赖它的其它模块不会受到影响。前后端分离流行后,前端开发从系统架构层面来看,实际上已经是站在食物链顶端的男人。

第一个金字塔模型

clipboard.png

从这个模型可以看出,前端作为消费者是站在最顶层的,它是Service层的消费者,所以只有保证Service层是健壮可靠,才能保证UI层的可靠。但是前端并不被其它层所依赖,因为它已经站在了顶点。所以除了最终用户我并不需要对其它层面负责,不用负责我凭什么还要写UT? 持不同意见的同学,稍安勿躁,我们继续深入分析

第二个金字塔模型

clipboard.png

前端也有自己的金字塔模型,我们在写UI的时候也会对底层代码有依赖,比如引入了mvvm framework, 要使用lodash之类的util库,要用到公共组件,当然我们可以自己实现这些前端较为底层的库,组件,但是绝大部分场景下,都是拿来主义,从github上找一个星星多的,并且默认认为这些开源库都已经做了充分的UT,是可靠的。而我们90%的精力都花在了更上层更顶端的业务代码上,我们依旧是站在食物链顶端的顶端,地位无可撼动,得出的结论是依旧不需要写UT

哪些情况下你可能需要写前端UT? 来做一组判断题

1. 你写的是个util类,是会被其他类调用的那种?
2. 你写的是一个公共component,是会被其他工程调用的那种?
3. 你写的是一个开源项目

如果以上3个问题有一个肯定回答,你都应该考虑写UT了。所以说,UT对于前端来讲,重不重要?重要!要不要写?看情况

需不需要写E2E测试?

看了上面的结论,严谨的同学可能已经要跳起来了,指着Up的鼻子说:“啊!谁说那90%的业务代码不被依赖的,我们并没有站在最顶端,我们上面还有客户还有上帝,这业务代码是要对客户负责的,所以我们还是要为这业务代码的健壮可靠性写UT”

能质疑这个问题的同学,非常好,非常秀,你的出发点是很好的,但是我们不妨换个角度来思考一下这个问题。首先,单元测试的存在有个前提,就是提供者和它上层的消费者需要在同一个特定的消费体系里,只有在同一个体系里,对提供者写UT才有意义,否则这是不牢靠的。要证明这一点很容易,比如我java写的一个util类,我肯定会在java这个特定的消费环境里来对这个util进行UT,因为它的上层消费者肯定也是java环境。一个前端的mvvm framework的UT肯定是基于JavaScript环境来写的,而不可能是别的语言,因为消费你的上层建筑也是基于JavaScript来调用的,当然有人说提供restful api的service层,虽然跟语言环境无关,但它都是基于http这个特定的消费环境的,或者也可以说接口层面的test已经属于整合测试的范畴。

但是,前端的业务代码却和上层的消费者——用户,不在同一个特定环境里,是脱节的,展现在用户面前的是GUI,用户通过一系列的用户事件,点击,输入,拖动,肉眼观测实现顶层消费。而不是呼出F12,在console里把前端的业务代码把调用一遍。所以说即使你的前端业务代码的UT做得再棒,理论上来说也是不可靠的。

那么,既然如此,如何保证业务代码是可靠的,能对消费者——用户负责呢?
说了这么多,终于引出了本文的重点——e2e自动化测试(以下简称Automation Test——AT),我可以使用各类e2e工具,模拟用户的操作行为来进行最终的验收测试,这样我不就跟用户是在同一个体系里了么?!且慢,同样先根据你项目的实际情况做几道是非题

1.测试团队是否兵强马壮  (基于人海战术的人肉e2e测试)
2.产品UI是否相对不稳定,经常大改 (改e2e case都来不及)
3.测试团队是否已经熟练掌握自动化测试技术,并已经运用起来  (QA来写e2e自动测试,理想国,前端就可以甩手了)
4.每一个迭代周期,留给QA测试时间是否充裕  (人肉e2e测试时间充足)
5.Service接口的测试覆盖率是否很高,后端UT的覆盖率是否很高 (底层建筑稳,隐患少)
6.每一个迭代周期,留给前端开发的时间是否很紧张 (前端写完业务代码,也要有时间写e2e代码)

如果你的答案里有2个以上的yes,可能e2e AT并不是现阶段必须要引入的,因为你背后有足够强大的测试团队支撑或者充裕的时间来保证产品被交付给用户前有足够的可靠性,或者是由于产品的特殊性,已经项目时间安排的不合理导致无法实施e2e AT

第三个金字塔模型

clipboard.png
e2e测试(不管是人肉的也好,自动化的也好)e2e测试在整个系统测试的贯通性覆盖率上来说是最大的,它可以覆盖从地基到顶端的这一长串的范围。所以如果说UT是用来保证保证成品各个层级可靠性的话,那e2e就是用来验证这种可靠性的,并且它的作用范围运不止此。

引入e2e AT

团队中,如果QA资源匮乏,且UI变化的频率不是很频繁的情况下,为了提高工作效率,提升产品质量了,我们就要考虑引入是该引入e2e自动化测试了来替代一部分的QA人肉工作量。谁来写e2e测试? 第一顺位继承人,前端当仁不让!

理由有3

1. 前端需要对自己写的业务代码负责,写UT又不可靠,那就只有写基与功能模块的AT了
2. 模拟用户操作,AT需要对各个DOM节点进行操作,前端对这个再熟悉不过
3. 有数款基于JavaScript的AT工具,学习成本低

e2e AT框架的选择

Selenium
目前市面上AT工具琳琅满目,种类繁多,up目前使用的是selenium + jest, selenium一般来说是雷打不动的,虽然我们主要使用的是它的webdriver功能,选择此类驱动型工具,up有个建议就是不要选择针对某种前端框架自带的自动化工具,诸如以前angular1时代up用过的protractor,它的写法对UI框架是强耦合的,且封装的API并不比比较原生的selenium高明多少,如若他日项目技术升级为Vue或者React之前写的AT case基本就废了,所以还是选用比较common的AT工具会比较好。

Jest
之前,up用的是mocha,jest虽然是为react量身打造,但是把它当成一个common的测试框架也挺好用,它有个比较有用的功能就是快照snapshot,为什么说快照很好用,是因为,亲手写过AT的朋友大多有些体会就是——AT写操作容易做判断却难,有了快照之后,就可以用2张快照进行比较,从而大大节省了取dom节点的text的代码,直接两张图一对比就玩事儿了。当然jest的快照功能你要自己实现也很方便,若想要保持现有的mocha或者jasmine一样可以引入快照功能。

一个e2e AT case的测试范围

根据Up的经验,我认为一个e2e case的范围最好以一个功能模块为最小单位,比如用户管理,一个case里我需要覆盖到:创建用户,查询用户,编辑用户,删除用户等4个基本操作。

//user.e2e.spec.js
const UserModule = require('UserModule');
const AuthModule = require('AuhtModule');
let userModule;
const userName = 'at_test_user';

beforeAll( ()=>{
    new AuthModule().login();
    userModule = new UserModule();
})

test('user:' + userName +'should not be exited', ()=> {
    userModule.find(userName);
    const image = webdriver.takeSnapshot();
    expect(image).toMatchSnapshot();
})

test('user:' + userName +'should be created successfully', ()=> {
    userModule.create(userName,pwd);
    const image = webdriver.takeSnapshot();
    expect(image).toMatchSnapshot();
})

//... other cases....

afterAll(()=>{
    webdriver.quit();
})

为什么不直接在spec里使用webdriver操作dom?

建议大家都给测试的功能对象封装一个类,比如上面伪代码里的UserModule

//UserModule.js
module.exports = function UserModule(){

    this.find = function(username){
        //...
        webdriver.findElement(xxxx).click();
    }
    this.create = function(user,pwd){
        //...
         webdriver.findElement(xxxx).input(xxxx);
        //...
        webdriver.findElement(xxxx).click();
    }
    // other operations
    

如果日后写其他case依赖于User的数据,那么就在before之类的地方进行userModule的调用来简历基础数据。这里不赘述了,OO的东西大家掌握的都比我好。

e2e AT的覆盖率

在人员有限的情况下,什么case需要些AT,那些不需要,或者不急着写,可以日后慢慢补

1. 新功能不需要急着写AT,应该交给QA人肉测试,待功能上线后再慢慢补齐
2. 反28原则: 28原则是指最重要的只占其中一小部分,约20%,其余80%尽管是多数却不重要,但是写AT需要反过来,我们优先写那80%不常用到的功能,至于那重要的20%,由于经常被使用,它能不能工作一目了然,其实也是最健壮的,需要写AT来覆盖的优先级就不显得这么高了,反而那不常用的80%功能往往没有经过大量的用户测试,很容易在某次迭代中产生新的bug。
3. 优先写happy path,优先保证一个功能模块的主线畅通,再写边界值测试。

e2e AT的重要性

先说说Regression Test即回归测试,当有新功能上线后,我们需要对产品老的功能进行回归测试,以确保新代码的加入没有引入新的bug。通常一次迭代中,QA会花费大约20~30%的时间进行回归测试,可见回归测试的重要性,但是很多情况下,由于项目时间紧迫,或者紧急发布等情况,被压缩和牺牲的往往是回归测试,而e2e AT正好可以覆盖一部分的回归测试,如果你的AT case覆盖率越高,则回归测试的覆盖率也越高,出品也就越稳定,如果AT能覆盖绝大部分的回归测试,而AT的执行效率又是人肉执行的数倍,那QA的工作量就被大大的降低。

e2e AT在开发流程中的位置(何时触发AT)

当新的代码合并到主线并部署到测试环境后,进入QA人肉测试环节前,是触发AT的最佳时机。bug是越早发现越好,AT与jenkins等CI工具可以很好的整合,也不依赖于什么特殊插件,跑完AT后自动生成report,若有失败则发送邮箱第一时间暴露问题,岂不美哉

几句话总结

关于前端UT:业务代码可以不写UT
关于前端e2e: 推荐用e2e AT来覆盖前端的业务代码,并纳入开发流程
另外,欢迎有持不同意见的同学参与讨论! 如果你有好的建议欢迎留言,毕竟是我一家之言,期待能碰撞出技术火花

阅读 18.1k

我把最美的青春都献给了代码
随便记录下用到的,学到的,看到的内容吧

现在sf的文章质量堪忧~~~

5.6k 声望
290 粉丝
0 条评论

现在sf的文章质量堪忧~~~

5.6k 声望
290 粉丝
文章目录
宣传栏