为什么你的Angular代码很难测试?

发表于:2015-8-28 10:39

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:冯尔东    来源:51Testing软件测试网采编

  Angular推出有好几年的时候了,跟其他的MV*框架相比,它的双向绑定,无须显式声明Model,模块管理,依赖注入等特点都给Web应用开发带来了极大的便利,另外,借助于它众多强大的原生directive,我们几乎可以避免麻烦的DOM操作了,除了这些,Angular还有一个很大的亮点,那就是高度的可测试性。
  今天的Web开发已经不同往日,更多的交互与逻辑都需要在前端完成,有时候,前端的代码量甚至在后端之上。怎么去保证如此多的前端逻辑不被破坏,依赖于功能测试?这显示不现实,功能测试很耗时,而且它的创建成本较高,所以通常只用它来覆盖最基本的那部分逻辑,另一方面,功能测试是依赖于流程的,如果你想验证购买页面上的某个前端逻辑,那么你就不得不一路从产品详情页面老老实实点过来,反馈时间太长了,可能你要等一分多钟才知道某个功能出错了,我们自然不想把宝贵的开发时间浪费在等待上。
  我在过去一段比较长的时候里都在项目上使用Angular,在感受到Angular带来的便利的同时,也饱受了Angular测试的折磨,因为我一直觉得Angular的单元测试很难写,跟JUnit+Mockito比起来,Angular代码的单元测试真是感觉写起来不得心应手,更别说用TDD的方式来驱动开发。
  我一直在思考为什么Angular社区说Angular的测试性很高,但是在项目上实现用起来却是另一番境地。经过分析项目上的代码,我觉得要想驱动测试开发Angular代码,那么其实是对你的Angular代码提出了比较高的要求,你要遵循Angular的风格来开发你的应用,只有你了解了其中的思想,你的测试写起来才会轻松。
  如果你已经使用Angular有一段时间了,但是还没有读过这篇文章,那么我强烈推荐你去读一下:ThinkinginAngular
  先来看一看怎么样的Angular代码才是苗正根红的Angular代码。
  1、避免使用任何的DOM操作
  像DOM操作这样的脏活累活都应该交给Angular的原生directive去做,我们的Angular代码应该只处理与DOM无关的业务逻辑。来看一个简单的例子,我们想创建一个简单的邮箱地址验证的directive,它要实现的功能是,当焦点从邮箱地址输入框移出的时候,对输入框中的邮箱地址进行验证,如果验证失败,则向输入框添加一个样式表示输入的地址不合法,比较糟糕的实现可能是这样的:
  上面的代码应该可以满足我们的要求(验证逻辑因为不是我们关注的重点,所以并不完善),而且这个directive实现起来也挺简单的,但是现在让我们一起来分析一下为什么我们认为这种写法是比较糟糕的。
  最简单的办法就是在你的directive里面去找所有与DOM操作相关的代码。
  首先看到的就是on()这个事件监听器。完全没有必要自己去监听发生在被directive修饰的元素上的事件,angular有一整套的原生directive来干这个事情,这里正确的做法应该是使用ng-blur来处理blur事件。
  下一个有问题的地方就是addClass(),angular除了提供了事件监听相关的directive外,也提供了操作元素本身属性的directive,ng-class就可以用来替换addClass()方法。
  按照这个思路修改后的代码:
  比较一下这两个版本的实现,是不是修改后的版本更简短,更容易理解一些。在新的版本里面,我们只处理了业务逻辑,即判断一个邮箱地址是否合法,至于何时触发验证,验证失败或成功之后应该有怎样的样式,我们都统统交给了angular原生directive去处理了。
  从测试的角度来看,如果想给第一个版本的实现写单元测试,那么要准备和验证的东西都很多,我们需要设法去触发对应元素的blur事件,然后再验证这个元素上是否添加了error-box这个class,根据我的经验,有时候为了验证这些DOM更新,你还不得不创建真实的DOM结构添加到DOMtree上去,又增加了一部分工作量。
  而版本二就简单多了,只定义了一个Model值isValid来标识当前的邮箱地址是否合法, validate()方法会在每次失焦之后自动执行,要为它添加单元测试,则只需要调用一下它的validate()方法,然后验证isValid的值就可以了。SO EASY!~
  2、将所有第三方服务封装成Service
  一个Web项目中总是无法避免地要使用一些第三方的服务,这里讨论的主要是前端的一些第三方服务,比如在线客服,站点统计等,这些代码都在我们的控制之外,大多数时候下都是从服务提供商的服务器上下载下来的,而我们需要在业务代码中调用这些代码。
  如果我们每次都是赤裸裸地以全局变量的形式来使用这些服务,那么造成的问题就是这样的代码很难测试,因为这些代码是不存在于我们的代码库中的,而且内容应该也是不定时更新的,大多数情况很多人会因为这些原因放弃到对这类操作的测试。
  假设我们现在需要在某些动作发生之后调用一个第三方服务,这个第三方服务叫做serviceLoadedFromExternal,它提供了一个API叫做makeServiceCall,如果直接使用这个API,那么在测试中很难去验证这个服务被执行了(因为在单元测试环境中这个服务根本不存在),但是如果我们将这个服务包装成一个angularservice,那么就可以在测试中轻易地将它替换成一个mock对象,然后验证这个mock对象上的方法被调用了就可以了。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号