在JavaScript的AngularJS库中进行单元测试的方法

  这篇文章主要介绍了在JavaScript的AngularJS库中进行单元测试的方法,主要针对AngularJS中的控制器相关,需要的朋友可以参考下

  开发者们都一致认为单元测试在开发项目中十分有好处。它们帮助你保证代码的质量,从而确保更稳定的研发,即使需要重构时也更有信心。


  测试驱动开发流程图

  AngularJS的代码声称其较高的可测性确实是合理的。单单文档中列出端对端的测试实例就能说明。就像AngularJS这样的项目虽然都说单元测试很简单但真正做好却不容易。即使官方文档中以提供了详尽的实例,但在我的实际应用中却还是很有挑战。这里我就简单示范一下我是怎么操作的吧.

  Instant Karma

  Karma 是来Angular团队针对JavaScript开发的一个测试运行框架。它很方便的实现了自动执行测试任务从而替代了繁琐的手工操作(好比回归测试集或是加载目标测试的依赖关系)Karma 和Angular的协作就好比花生酱和果冻.

  只需要在Karma中定义好配置文件启动它,接下来它就会在预期的测试环境下的自动执行测试用例。你可以在配置文件中制定相关的测试环境。angular-seed,是我强烈推荐的可以快速实施的方案。在我近期的项目中Karma 的配置如下:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

module.exports = function(config) {
config.set({
basePath: '../',
 
files: [
'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,
 
frameworks: ['jasmine'],
 
browsers: ['PhantomJS'],
 
plugins: [
'karma-junit-reporter',
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-jasmine',
'karma-phantomjs-launcher'
],
 
junitReporter: {
outputFile: 'test_out/unit.xml',
suite: 'unit'
}
 
})
}

  这个跟angular-seed的默认配置类似只不过有以下几点不同:

  需要更改浏览器从Chrome 转到PhantomJS, 这样每次跳转时无需再打开新的浏览器窗口,但在OSX系统会有窗口延迟。所以这个插件还有浏览器设置都做了更改。

  由于我的应用需要引用Google的Recaptcha服务因此添加了依赖的recaptcha_ajax.js小文件。这个小配置就像在Karma的配置文件中添加一行代码那么简单。

  autoWatch确实是个很酷的设置,它会让Karma在有文件更改时自动回归你的测试用例。你可以这样安装Karma:

  ?

  1npm install karma

  angular-seed 提供了一个简单的脚本inscripts/test.sh去触发Karma的测试。

  用Jasmine设计测试用例

  当使用Jasmine----一种行为驱动开发模式的JavaScript测试框架为Angular设计单元测试用例时大部分的资源都已可获取。

  这也就是我接下来要说的话题。

 

  如果你要对AngularJS controller做单元测试可以利用Angular的依赖注入dependency injection 功能导入测试场景中controller需要的服务版本还能同时检查预期的结果是否正确。例如,我定义了这个controller去高亮需要导航去的那个页签:

 

?

1
2
3
4
5

app.controller('NavCtrl', function($scope, $location) {
$scope.isActive = function(route) {
return route === $location.path();
};
})

       如果想要测试isActive方法,我会怎么做呢?我将检查$locationservice 变量是否返回了预期值,方法返回的是否预期值。因此在我们的测试说明中我们会定义好局部变量保存测试过程中需要的controlled版本并在需要时注入到对应的controller当中。然后在实际的测试用例中我们会加入断言来验证实际的结果是否正确。整个过程如下:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

describe('NavCtrl', function() {
var $scope, $location, $rootScope, createController;
 
beforeEach(inject(function($injector) {
$location = $injector.get('$location');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
 
var $controller = $injector.get('$controller');
 
createController = function() {
return $controller('NavCtrl', {
'$scope': $scope
});
};
}));
 
it('should have a method to check if the path is active', function() {
var controller = createController();
$location.path('/about');
expect($location.path()).toBe('/about');
expect($scope.isActive('/about')).toBe(true);
expect($scope.isActive('/contact')).toBe(false);
});
});

  使用整个基本的结构,你就能设计各种类型的测试。由于我们的测试场景使用了本地的环境来调用controller,你也可以多加上一些属性接着执行一个方法清除这些属性,然后再验证一下属性到底有没有被清除。

  $httpBackendIs Cool

  那么要是你在调用$httpservice请求或是发送数据到服务端呢?还好,Angular提供了一种

  $httpBackend的mock方法。这样的话,你就能自定义服务端的响应内容,又或是确保服务端的响应结果能和单元测试中的预期保持一致。

 

  具体细节如下:

 

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

describe('MainCtrl', function() {
var $scope, $rootScope, $httpBackend, $timeout, createController;
beforeEach(inject(function($injector) {
$timeout = $injector.get('$timeout');
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
 
 
var $controller = $injector.get('$controller');
 
createController = function() {
return $controller('MainCtrl', {
'$scope': $scope
});
};
}));
 
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
 
it('should run the Test to get the link data from the go backend', function() {
var controller = createController();
$scope.urlToScrape = 'success.com';
 
$httpBackend.expect('GET', '/slurp?urlToScrape=http:%2F%2Fsuccess.com')
.respond({
"success": true,
"links": ["http://www.google.com", "http://angularjs.org", "http://amazon.com"]
});
 
// have to use $apply to trigger the $digest which will
// take care of the HTTP request
$scope.$apply(function() {
$scope.runTest();
});
 
expect($scope.parseOriginalUrlStatus).toEqual('calling');
 
$httpBackend.flush();
 
expect($scope.retrievedUrls).toEqual(["http://www.google.com", "http://angularjs.org", "http://amazon.com"]);
expect($scope.parseOriginalUrlStatus).toEqual('waiting');
expect($scope.doneScrapingOriginalUrl).toEqual(true);
});
});

  正如你所见,beforeEach call其实都很类似,唯一不同的是我们是从injector获取$httpBackend而并非直接获取。即使如此,创建不同的测试时还会有一些明显的不同之处。对初学者来说,会有一个afterEachcall 方法来确保$httpBackend在每次用例执行后不会有明显的异常请求。如果你观察一下测试场景的设置和$httpBackend方法的应用就会会发现有那么几点不是那么直观的。

  实际上调用$httpBackend的方法也算是简单明了但还不够——我们还得在传值给$scope.$apply的方法中把调用封装到实际测试中的$scope.runTest方法上。这样在$digest被触发后才能处理HTTP请求。而如你所见直到我们调用$httpBackend.flush()方法后$httpBackend才会被解析,这也就保证了我们能在调用过程中去验证返回的结果是否正确(在上面的示例中,controller的$scope.parseOriginalUrlStatusproperty属性将被传递给调用者,我们也因此能实时监控)

  接下来的几行代码都是在调用过程中检测$scopethat属性的断言。很酷吧?

  提示:在某些单元测试中,用户习惯把没有$的范围标记为变量。这个在Angular文档中并没有强制要求或是过分强调,只是我在使用中为了提高可读性和一致性才使用$scopelike这种方式。

  结论

 

  也许这就是我做起来对其他人而言只是自然而然能做到的事情之一,但是学习使用Angular编写单元测试一开始对我而言确实是相当痛苦的。我发现自己对如何开始的理解大多来自互联网上各种博客文章和资源的拼拼凑凑,没有真正一致或明确的最佳实践,而是通过自然而然随意的选择。我想针对我最终得到的成果提供一些文档,以帮助那些也许还在坑里面挣扎的其他人,毕竟他们只是想要编写代码而已,而非不得不去了解Angular和Jasmine中所有的怪异特性和独特用法。因此我希望这篇文章能对你有些许帮助。

 

时间: 2024-12-23 18:44:13

在JavaScript的AngularJS库中进行单元测试的方法的相关文章

使用JavaScript的AngularJS库编写hello world的方法

本文展示了AngularJS框架实现的hello world代码示例. 如下是一些你在看Hello World 示例和接下来的代码示例时需要重点关注的方面. ng-app, ng-controller, ng-model 指令 带有两个大括弧的模板 步骤 1: 在<Head>部分包含Angular Javascript 将下面的代码包含入 <head></head> 中,以引入 Angularjs javascript 文件. 可以用如下写法从 Google 管理的库

在JavaScript的jQuery库中操作AJAX的方法讲解_jquery

Java软件开发中,后台中我们可以通过各种框架,像SSH等进行对代码的封装,方便我们对Java代码的编写,例如,Struts,SpringMVC对从前台到action的流程进行封装控制,使我们只需要进行一些简单配置就可以实现:而Spring进行了对各种对象的管理进行封装,提供了AOP编程的方式,大大方便了我们:而Hibernate和IBatis则是对JDBC代码进行封装,不需要我们每次都写那些重复而繁杂的JDBC代码.   前台呢,对于页面一些效果,验证等,我们都是通过JavaScript语言进

详解JavaScript的AngularJS框架中的作用域与数据绑定_AngularJS

AngularJS 简介AngularJS 是由 Google 发起的一款开源的前端 MVC 脚本框架,既适合做普通 WEB 应用也可以做 SPA(单页面应用,所有的用户操作都在一个页面中完成).与同为 MVC 框架的 Dojo 的定位不同,AngularJS 在功能上更加轻量,而相比于 jQuery,AngularJS 又帮您省去了许多机械的绑定工作.在一些对开发速度要求高,功能模块不需要太丰富的非企业级 WEB 应用上,AngularJS 是一个非常好的选择.AngularJS 最为复杂同时

再JavaScript的jQuery库中编写动画效果的指南_基础知识

jquery中常用的动画的方法就是hide()与show(). $(element).hide()这段代码可以与这相等element.css("display","none")  在hide(time)与show(time)中填入事件,可以慢慢消失跟显现.可以修改元素的多个样式,高度,宽度,不透明度. 另一组方法fadeIn()与fadeOut()这个与hide跟show不同的是,当使用hide或者show的时候会改变网页的高度,而fadeIn与fadeOut则不会

详解JavaScript的AngularJS框架中的表达式与指令_AngularJS

"指令属性"就是绑定在DOM元素上的函数,它可以调用方法.定义行为.绑定controller及$scope对象.操作DOM,等等等等. 当浏览器启动.开始解析HTML(像平时一样)时,DOM元素上的指令属性就会跟其他属性一样被解析. 当一个Angular.js应用启动,Angular编译器就会遍历DOM树(从有ng-app指令属性的那个DOM元素开始,如我们在本系列第一篇里所提过的),解析HTML,寻找这些指令属性函数. 当在一个DOM元素上找到一个或多个这样的指令属性函数,它们就会被

JavaScript的jQuery库中ready方法的学习教程_jquery

学习 jQuery 有许多途径,我们今天从 jQuery 的 ready 函数开始.本例中的代码都来自于 jQuery 脚本库. 如果你使用过 jQuery , 就必然使用过 ready 函数,它用来注册当页面准备好之后可以执行的函数. 问题来啦,我们的页面什么时候准备好了呢? onload 事件最基本的处理方式就是页面的 onload 事件,我们在处理这个事件的时候,可以有多种方式,即可以通过 HTML 方式,直接写在 body 元素的开始标记中,也可以使用事件注册的方式来使用,这又可以分为

JavaScript的jQuery库中function的存在和参数问题_jquery

jQuery function 参数传递 jQuery的function函数中使用外部变量: //如何取得i的变量 for(i=0;i<3;i++) { $.get("/test.html", function(data){ alert(i) }); } //使用闭包: for(i=0;i<3;i++) { (function(index){ $.get("/test.html", function(data){ alert(index) }); })(

JavaScript快速切换繁体中文和简体中文的方法及网站支持简繁体切换的绝招_javascript技巧

一般商业网站都有一个语言的需求,就是为了照顾使用正体中文的国人,会特地提供一个切换到正体中文的选项(或曰"繁体中文").传统做法是在服务端完成的,即通过某些控件或者过滤器转换文本语言.这里笔者介绍一种简单可行的方法,不是在服务端而是利用前端的 JavaScript 就可以切换正体中文. 大概六年前我还专门写过这议题的博文,这篇也是奠基在那篇文章(旧文也是参考了一高手代码)之上,<对 JavaScript 繁简字切换的小改进>.时过境迁,有必要把代码进行更新一下--以前的太稚

顶级的JavaScript框架、库、工具及其使用

几乎每隔一个星期,就有一个新的 JavaScript 库席卷网络社区!Web 社区日益活跃.多样,并在多个领域快速成长.想要研究每一个重要的 JavaScript 框架和库,是个不可能完成的任务.接下来,我会分享一些前端开发的最著名和最有影响力的框架和库.下面,就让我们一起来看看,顶级的 JavaScript web 前端框架.库和工具及其使用. 请注意: 如果没有包括你最喜欢的 JavaScript 的框架和库,请多包涵. 请实时更新你的框架和库,最新版本往往有更好的跨浏览器和跨设备支持.可以