开发人员都同意单元测试在开发项目中非常有益。它们帮助您确保代码的质量,从而确保更稳定的R&D和更大的信心,即使在需要重构的时候。
测试驱动开发流程图
AngularJS的代码声称它的高可测试性真的很合理。只有文档中列出的端到端测试用例可以说明这一点。像AngularJS这样的项目说单元测试很简单,但是要做好它并不容易。即使在官方文件中提供了详细的例子,但在我的实际应用中仍然非常具有挑战性。在这里,我将简单地演示如何操作它。
瞬间因果报应
Karma是Angular团队开发的JavaScript测试运行框架。自动执行测试任务而不是繁琐的手动操作(如回归测试集或加载目标测试的依赖项)非常方便。Karma和Angular之间的合作就像花生酱和果冻。
只需在Karma中定义一个配置文件启动它,然后它就会在预期的测试环境中自动执行测试用例。您可以在配置文件中设置相关的测试环境。我强烈推荐的Angular-seed可以快速实现。我最近的项目中Karma的配置如下:
module . exports=function(config){ config . set({ base path : './',文件:[' app/lib/angular/angular . js ',' app/lib/angular/angular-*。' js ',' app/js/**/*。js ',' test/lib/recaptcha/recaptcha _ Ajax . js ',' test/lib/angular/angular-mocks . js ',' test/unit/**/*。js' ],exclude :[' app/lib/angular/angular-loader . js ',' app/lib/angular/*.min.js ',' app/lib/angular/angular-scenario . js '],autoWatch: true,framework :[' jasmine '],browsers: ['PhantomJS'],plugins :[' Karma-JUnit-reporter ',' karma-chrome-launcher ',' karma-firefox-launcher ' '
你需要把浏览器从Chrome换成PhantomJS,这样就不需要每次跳转都打开一个新的浏览器窗口,但是在OSX系统中会有窗口延迟。所以插件和浏览器设置都被更改了。因为我的应用程序需要引用谷歌的Recaptcha服务,所以我添加了依赖的recaptcha_ajax.js小文件。这个小配置就像在Karma的配置文件中添加一行代码一样简单。AutoWatch真的是一个很酷的设置,当有文件更改时,它会让Karma自动返回到你的测试用例中。你可以这样安装Karma:
Npm install Karmaangular-seed提供了一个简单的脚本来触发因果报应测试。
用Jasmine设计测试用例
当使用行为驱动的JavaScript测试框架Jasmine为Angular设计单元测试用例时,大部分资源都是可用的。
这就是我接下来要讲的。
如果要在AngularJS控制器上做单元测试,可以使用Angular依赖注入功能,在测试场景中导入控制器所需的服务版本,同时检查预期结果是否正确。例如,我定义了这个控制器来突出显示需要导航的选项卡:
app.controller('NavCtrl ',function($scope,$ location){ $ scope . isaactive=function(route){ return route===$ location . path();};})如果我想测试isActive方法,我会怎么做?我将检查$locationservice变量是否返回预期值,以及该方法是否返回预期值。因此,在我们的测试指令中,我们将定义局部变量来保存测试过程中所需的受控版本,并在必要时将其注入相应的控制器。然后我们将在实际测试用例中添加断言,以验证实际结果是否正确。整个过程如下:
description(' nav ctrl ',function() { var $scope,$location,$rootScope,createControllerbefore EARN(inject)(function($ injector){ $ location=$ injector。get($ location));$ ROOTSCOpe=$ injector。get($ ROOTSCOpe’);$scope=$rootScope .$ new();var $控制器=$注射器。get(' $ controller ');create controller=function(){ return $ controller(' nav ctrl ',{ ' $ scope ' : $ scope });};}));它('应该有一个检查路径是否活动的方法,function(){ var controller=create controller();$位置。路径('/about ');expect($location.path()).toBe('/约');expect($ scope)。I active('/about ').toBe(真);expect($ scope)。isaactive('/contact ').toBe(假);});});使用整个基本的结构,你就能设计各种类型的测试。由于我们的测试场景使用了本地的环境来调用控制器,你也可以多加上一些属性接着执行一个方法清除这些属性,然后再验证一下属性到底有没有被清除。
$httpBackendIs酷
那么要是你在调用$httpservice请求或是发送数据到服务端呢?还好棱角分明提供了一种
$httpBackend的模拟的方法。这样的话,你就能自定义服务端的响应内容,又或是确保服务端的响应结果能和单元测试中的预期保持一致。
具体细节如下:
description(' MainCtrl ',function() { var $scope,$rootScope,$ httpBackend,$timeout,createControllerbefore EARN(inject)(function($ injector){ $ time out=$ injector。get($ time out));$ Httpbackend=$ injector。get($ Httpbackend ');$ ROOTSCOpe=$ injector。get($ ROOTSCOpe’);$scope=$rootScope .$ new();var $控制器=$注射器。get(' $ controller ');create controller=function(){ return $ controller(' MainCtrl ',{ ' $ scope ' : $ scope });};}));在EACH(function(){ $ Httpbackend)之后。verifynooutstanding experience();$ Httpbackend。verifynooutstanding request();});它('应该运行试验从去后端获取链接数据,函数(){ var controller=create controller();$ scope。urltoscrape='成功。com ';$httpBackend.expect('GET ','/slurp?urlToScrape=http ://成功。com ').回应({ ' success ' : true,' links ' :[' http://www .谷歌。' com ',' http://angularjs.org ','亚马逊http://。com '])};//必须使用$apply来触发$digest,它将//处理超文本传送协议请求$作用域$ apply(function(){ $ scope。runtest();});expect($ scope)。parseoriginalurlstatus ).toe qal(' calling ');$ Httpbackend。flush();expect($scope.retrievedUrls).toeqal([' http://www .谷歌。' com ',' http://angularjs.org ','亚马逊http://。com ']);expect($ scope)。parseoriginalurlstatus ).toEqual('等待');expect($ scope)。doneclapinginarininarurl).toEqual(真);});});正如你所见,每次通话前其实都很类似,唯一不同的是我们是从注射器获取$httpBackend而并非直接获取。即使如此,创建不同的测试时还会有一些明显的不同之处。对初学者来说,会有一个每次通话后方法来确保$httpBackend在每次用例执行后不会有明显的异常请求。如果你观察一下测试场景的设置和$httpBackend方法的应用就会会发现有那么几点不是那么直观的。
实际上调用$httpBackend的方法也算是简单明了但还不够——我们还得在传值给$scope .$apply的方法中把调用封装到实际测试中的$scope.runTest方法上。这样在$摘要被触发后才能处理超文本传送协议请求。而如你所见直到我们调用$httpBackend.flush()方法后$httpBackend才会被解析,这也就保证了我们能在调用过程中去验证返回的结果是否正确(在上面的示例中控制器的$ scope。parseoriginalurlstatusperty属性将被传递给调用者,我们也因此能实时监控)
接下来的几行代码都是在调用过程中检测$scopethat属性的断言。很酷吧?
提示:在一些单元测试中,用户习惯于将不带$的范围标记为变量。这在Angular文档中并不是强制性的或者过分强调的,但是为了提高可读性和一致性,我只使用了$scopelike。
结论
也许这是我自然可以为别人做的事情之一,但是一开始学习使用Angular编写单元测试对我来说真的很痛苦。我发现自己对于如何开始的理解,大多来自于网络上各种博客文章和资源的拼凑,并没有真正一致或清晰的最佳实践,而是通过自然和随机的选择。我想为我的最终成绩提供一些文件,以帮助其他可能还在坑里挣扎的人。毕竟他们只是想写代码,而不是非要了解Angular和Jasmine中所有奇怪的特性和独特的用法。因此,我希望这篇文章能对你有所帮助。