七牛前端测试实践

f627 9年前

测试是完善的研发体系中不可或缺的一环,但是在前端开发的项目中做单一测试的相对较少。为让大家了解前端测试给项目带来的好处,七牛资深前端工程师马逸清结合技术开发团队在前端测试方面的积极探索,特此分享以下前端测试的实践经验。


理想中的代码结构,每个模块都应该比较简单,且每个模块之间的关系也应该非常清晰。但事实上,在做Web开发的过程中,随着功能和迭代次数越来越多,就会产生一些问题。最初,七牛的Web开发并没有写测试,在开发的过程中常遇到代码稍作修改便会产生Bug的情况。而在做一些新功能开发时又很难察觉到它会对旧功能产生影响,到上报之后才发现有问题。另外,代码放久了,也不敢重构。因此,七牛开始考虑做前端测试。


七牛前端测试的早期尝试


七牛最早的尝试是使用Selenium+Phantom,通过模拟用户操作来进行测试。比如,登录过程的代码大致如下:


七牛前端测试实践


测试过程就是打开某个页面之后,输入用户名、密码,点击这个按钮模拟用户的操作过程。这部分的工作其实是在做端到端的测试,而实际项目中,业务逻辑相对复杂,编写端到端测试工作量比较大,测试代码也会很复杂。因此,这样的做法没有很好地发扬起来。


既然做完整测试比较麻烦,七牛前端团队决定做一些更小粒度的测试,对一些基础函数编写单元测试。他们会抽取一些基本函数和组件放在统一的模块中,编写单元测试去验证函数是否符合预期。测试粒度变小以后,相比端到端测试,写测试变得更加容易,但对于DOM依赖比较强的部分,就会难以测试。


举个例子,设置七牛开发者平台顶部导航和侧边栏导航选中状态的功能是通过jQuery 扩展的方式实现的,主要通过匹配URL和导航栏 A标签中href属性来判断是否应该选中。这里的各种子页面URL设计比较杂乱,非常容易出错,所以在编写测试的时候,需要把DOM结构写进测试脚本。由于一些历史遗留问题,七牛的导航栏有着两种结构,因此,这一部分的测试变得非常痛苦,DOM结构发生一些微小的变化,整个测试脚本都要跟着改。所以对于依赖DOM结构的功能测试还比较少,并没有做到完全覆盖。


在实际开发中要尽量避免逻辑和DOM的耦合,分离之后才能更加方便地去验证业务逻辑。使用MVC框架是一个比较好的方式,关注度分离,让DOM和业务逻辑解耦。


七牛新的项目选择了AngularJS,AngularJS对测试很友好,不允许在Controller里操作DOM和依赖注入都让测试工作更加方便。换了AngularJS之后,测试的部分主要分为三类:一是Utils,包括一些格式化的函数、验证的函数;二是Service里的Model状态;三是View状态,这些和具体展现以及业务逻辑都有关,是比较复杂的部分。


Utils的测试


七牛前端测试实践


以上是对于Utils测试的代码,在代码里注入相关模块就可以进行测试(网上有很多相关的教程,对于Utils的测试比较简单)。接下来是做Service的测试,在这个过程中需要做一下设置。如下图所示:


七牛前端测试实践


Service封装了很多HTTP请求,这样会依赖于API服务,为此,我们使用AngularJS自带的Mock模块将HTTP这一层处理掉,使用Mock模块在测试时不会发出HTTP请求。同时也可以通过这个模块验证发出的请求以及请求的参数等。再接下来是Controller的测试,主要测一些外围的函数和变量的状态。如以下代码:


七牛前端测试实践


七牛前端测试实践


在实际的测试过程中,团队会遇到一些依赖不好处理的情况,比如依赖当前时间或者随机字符串,使用Stub就可以比较好的处理这种情况。如用户登录后会记录登录的时间,每次请求都会先用当前时间和登录时间进行比较,判断登录是否过期,这里就会依赖当前时间,由于每次运行测试脚本的时间不确定,因此需要替换掉当前时间。七牛使用的是sinon.js,把某个对象当中的某个方法替换掉,一旦每次调用当前时间,它就变成一个固定的值,后面的测试就跟以前的基本方式一样。具体方法如下:


七牛前端测试实践


在做测试的时候,某些逻辑代码依赖的是外部的返回值,这时用Stub最合适。因为Stub在Sinon库里可以选择每一次调用的返回值,或者某个参数的返回值,这样能满足绝大部分需求。


Mock除了关注外部依赖的返回值外,还关注外部调用的值。因此,在存/写一些状态的时候,没有返回值就不知道存/写是否成功,只能测试是否调用到了外部依赖的某个方法,传进去的参数是否是预期的。使用Mock的方式,最开始调用时要验证某个Mock对象的某个行为,最后再由Mock对象去验证它的行为是否正确。手工写一个Mock的对象会很麻烦,一般会使用通用的一些函数库来减少工作量。


在测试过程中,大部分情况下用Stub就足够了。如果一些测试依赖某些返回值,但又没有返回值,该怎么办?这里可以用Spy对象去验证一些函数是否被调用、调用时的参数等,Spy就是带有行为验证的Stub。


有了MVC框架和Stub,基本上单元测试中的问题就都能解决了,但是实践中还存在对于Mock Data处理的小问题。七牛团队最初的做法是直接在测试里硬编码了Mock数据,但测试脚本多了以后难以维护,后来便更换了方式,使用了Karma-fixture插件。由于前后端的同时进行,开发时后端API往往没有准备好,所以这里会先构造一些Mock数据,以Json文件的形式放在Mock目录下,再运行一个静态Server专门提供这些Mock数据。使用这样的插件,在测试代码里面便可以把这些Mock数据加载进来直接使用。这样做的好处在于,大家在开发调试和写单元测试时,使用的是同样一套Mock数据。将来如果结构发生一些小的调整(如加字段),也只需要做简单的修改。


目前用到的工具:Karma+PhantomJS


在七牛早期项目中,测试工具主要使用Selenium,但Selenium配置比较复杂。因此,七牛在新项目开始使用Karma+PhantomJS,Karma比Selenium更加轻量,更适合运行单元测试。而脚本运行的浏览器环境选择PhantomJS,可以直接在命令行里看到结果,不会每次运行测试时弹出界面。测试框架官方推荐Jasmine(包括Stub、Spy的功能),虽然Jasmine的断言以及Stub功能不如Mocha+Chai+Sinon强大,但对于实际项目已经足够。此外,测试还会依赖一些Karma插件,如测试覆盖率Karma-coverage工具、Karman-fixture工具及Karma-coffee处理工具。此外,前端社区里提供里比较丰富的插件,常见的测试需求都能涵盖到。


七牛前端测试实践


七牛前端测试实践


工具的配置可按照以上两图的代码先把Karma的文件配置好,然后再配置到自己的gulpfile里。配置gulp如下:


七牛前端测试实践


gulp主要配置了两个地方,一是只跑一次测试的testOnce,二是测试代码的testAndWatch。每次进行修改、保存,它都能自动的再跑,这样可以一边开发一边跑测试,从而查看对当前的代码有没有影响。


有了这部分的测试环境,可以把它配置到持续集成的环境当中。由于现有的工具已经非常成熟,这里可以导出覆盖率报告进行分析,之后还可以改进mockData。如果将来业务变得更加复杂,可以考虑加入UI的自动化测试。


前端测试带来的益处


首先,写测试代码和写业务代码时思考方式不一样,写测试代码的时候会考虑业务逻辑的边际条件是什么,这样就能提前找到开发时不太容易找到的Bug。当思路还在功能上时,去修改这个Bug会相对比较轻松,如果后面发现再去修改,就会花更多的工夫。


另一个好处是在写业务代码的时候,理解会更深刻。写测试时会发现代码不好测,输入输出不合理的情况。写测试可以帮助你找到更合理的代码结构。另外,如果很多人共同维护一份代码又需要对公共组件做一些小修改的时候,有了测试改起来会非常安心,测试覆盖比较好的情况下,会减少修改代码的心理负担。


前端的框架非常多,大家可以自由选择适合自己业务的框架。现阶段,前端MVC框架发展非常迅猛,它把DOM的结构和业务累计分离开来,使得测试比以前更好做。此外,Node.js的发展对于后端的测试非常重要,它有一些完善的工具链,在测试方面不会遇到阻碍,因为搭建一个测试环境已经变得非常简单。有了以上的测试环境,在写测试的过程中,更多的便是一个理想和现实之间的平衡。


关于前端测试的建议


对于一些规模较小、人手不足的公司,开发人员写测试的总会遇到一些影响因素,如:


  • 迭代速度快;

  • 业务需求不稳定,有时手里的代码还未写完,又需要修改功能;

  • 人手不足,把功能代码写完已经需要加班,但还需要写测试代码。


大部分的实际情况比解决方案要复杂。在测试的时候,可能要模拟多种实际情况,而业务代码则有可能就是实际的解决方案。对于比较大型的产品,测试代码比业务代码更多,项目紧张或者功能需求不稳定的情况下,过度测试不不可取。


目前七牛的做法是有选择性的写测试。首先,七牛会测试比较重要的部分,评估一些风险。比如数据统计、计费等和用户密切相关的内容。其次是测试比较稳定的部分,即跟业务不相关的部分函数。再次是测试依赖比较少的部分,这里只需要关注它整个逻辑本身。


在人手不足、时间有限的情况下,可能无法很快加上测试环节,这时一定要养成验证的习惯。随着公司业务发展,项目逐渐变大的时候,为了保持代码的可维护性,进行测试变得非常有必要,特别是在开发过程中,它能帮助你找到一些不容易察觉的小Bug。一些开发人员可能认为写测试会耗费额外的时间,但是对于大型软件项目,合适的测试方案能够大大减少今后的维护成本,从长远来看,写测试所花的时间是非常值得的。

来自:http://weibo.com/p/1001603864951990185052