在JavaScript的AngularJS库中进行单元测试的方法  更新时间:2015年06月23日 09:48:36   投稿:goldensun   这篇文章主要介绍了在JavaScript的AngularJS库中进行单元测试的方法,主要针对AngularJS中的控制器相关,需要的朋友可以参考下 开发者们都一致认为单元测试在开发项目中十分有好处。它们帮助你保证代码的质量,从而确保更稳定的研发,即使需要重构时也更有信心。  测试驱动开发流程图 AngularJS的代码声称其较高的可测性确实是合理的。单单文档中列出端对端的测试实例就能说明。就像AngularJS这样的项目虽然都说单元测试很简单但真正做好却不容易。即使官方文档中以提供了详尽的实例,但在我的实际应用中却还是很有挑战。这里我就简单示范一下我是怎么操作的吧. Instant Karma Karma 是来Angular团队针对JavaScript开发的一个测试运行框架。它很方便的实现了自动执行测试任务从而替代了繁琐的手工操作(好比回归测试集或是加载目标测试的依赖关系)Karma 和Angular的协作就好比花生酱和果冻. 只需要在Karma中定义好配置文件启动它,接下来它就会在预期的测试环境下的自动执行测试用例。你可以在配置文件中制定相关的测试环境。angular-seed,是我强烈推荐的可以快速实施的方案。在我近期的项目中Karma 的配置如下:   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:   npm install karma angular-seed 提供了一个简单的脚本inscripts/test.sh去触发Karma的测试。 用Jasmine设计测试用例 当使用Jasmine—-一种行为驱动开发模式的JavaScript测试框架为Angular设计单元测试用例时大部分的资源都已可获取。 这也就是我接下来要说的话题。 如果你要对AngularJS controller做单元测试可以利用Angular的依赖注入dependency injection 功能导入测试场景中controller需要的服务版本还能同时检查预期的结果是否正确。例如,我定义了这个controller去高亮需要导航去的那个页签:   app.controller(‘NavCtrl’, function($scope, $location) { $scope.isActive = function(route) { return route === $location.path(); }; }) 如果想要测试isActive方法,我会怎么做呢?我将检查$locationservice 变量是否返回了预期值,方法返回的是否预期值。因此在我们的测试说明中我们会定义好局部变量保存测试过程中需要的controlled版本并在需要时注入到对应的controller当中。然后在实际的测试用例中我们会加入断言来验证实际的结果是否正确。整个过程如下:   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方法。这样的话,你就能自定义服务端的响应内容,又或是确保服务端的响应结果能和单元测试中的预期保持一致。 具体细节如下:   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:success.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中所有的怪异特性和独特用法。因此我希望这篇文章能对你有些许帮助。 您可能感兴趣的文章: angularjs中的单元测试实例 使用Angular CLI进行单元测试和E2E测试的方法 AngularJs unit-testing(单元测试)详解 利用Jasmine对Angular进行单元测试的方法详解 AngularJS 单元测试(一)详解 AngularJS 单元测试(二)详解 对Angular.js Controller如何进行单元测试 Angular单元测试之事件触发的实现 浅谈Angular单元测试总结 Angular进行简单单元测试的实现方法实例 AngularJS 相关文章 Angular封装搜索框组件操作示例 这篇文章主要介绍了Angular封装搜索框组件操作,结合实例形式分析了基于Angular组件库实现搜索功能的封装操作相关实现步骤与注意事项,需要的朋友可以参考下 2019-04-04 对比分析AngularJS中的$http.post与jQuery.post的区别 这篇文章主要给大家对比分析AngularJS中的$http.post与jQuery.post的区别,十分的详细,是篇非常不错的文章,这里推荐给小伙伴们。 2015-02-02 AngularJS创建一个上传照片的指令实例代码 这篇文章主要介绍了AngularJS创建一个上传照片的指令实例代码,需要的朋友可以参考下 2018-02-02 AngularJs中route的使用方法和配置 angular是Google开发的一个单页面应用框架,是现在比较主流的单页面应用框架之一,下面通过本文给大家介绍AngularJs中route的使用方法和配置,感兴趣的朋友一起学习吧 2016-02-02 Angular项目如何升级至Angular6步骤全纪录 这篇文章主要给大家介绍了关于Angular项目如何升级至Angular6的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 2018-09-09 探讨AngularJs中ui.route的简单应用 这篇文章主要介绍了AngularJs中ui.route的简单应用,非常不错,具有参考借鉴价值,需要的朋友可以参考下 2016-11-11 Angular 容器部署的方法 这篇文章主要介绍了Angular 容器部署的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 2018-04-04 详解支持Angular 2的表格控件 本文主要对支持Angular2的表格控件进行详细介绍。具有一定的参考价值,下面跟着小编一起来看下吧 2017-01-01 AngularJS constant和value区别详解 angularJS可以通过constant(name,value)和value(name,value)对于创建服务也是很重要的。他们之间有什么不同呢?今天小编给大家分享AngularJS constant和value区别详解,需要的朋友参考下 2017-02-02 Angular4 组件通讯方法大全(推荐) 这篇文章主要介绍了Angular4 组件通讯方法大全(推荐),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 2018-07-07 最新评论

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

201562393334308.png (257×221)

 测试驱动开发流程图

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

Instant Karma

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

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

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:
 

npm install karma

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

用Jasmine设计测试用例

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

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

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

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

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

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方法。这样的话,你就能自定义服务端的响应内容,又或是确保服务端的响应结果能和单元测试中的预期保持一致。

具体细节如下:
 

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中所有的怪异特性和独特用法。因此我希望这篇文章能对你有些许帮助。

您可能感兴趣的文章:

  • angularjs中的单元测试实例
  • 使用Angular CLI进行单元测试和E2E测试的方法
  • AngularJs unit-testing(单元测试)详解
  • 利用Jasmine对Angular进行单元测试的方法详解
  • AngularJS 单元测试(一)详解
  • AngularJS 单元测试(二)详解
  • 对Angular.js Controller如何进行单元测试
  • Angular单元测试之事件触发的实现
  • 浅谈Angular单元测试总结
  • Angular进行简单单元测试的实现方法实例
张贴在3