angularjs中的单元测试实例


Posted in Javascript onDecember 06, 2014

当ng项目越来越大的时候,单元测试就要提上日程了,有的时候团队是以测试先行,有的是先实现功能,后面再测试功能模块,这个各有利弊,今天主要说说利用karma和jasmine来进行ng模块的单元测试.

什么是Karma

karma是一个单元测试的运行控制框架,提供以不同环境来运行单元测试,比如chrome,firfox,phantomjs等,测试框架支持jasmine,mocha,qunit,是一个以nodejs为环境的npm模块.

安装测试相关的npm模块建议使用----save-dev参数,因为这是开发相关的,一般的运行karma的话只需要下面两个npm命令

npm install karma --save-dev

npm install karma-junit-reporter --save-dev

安装karma的时候会自动的安装一些常用的模块,参考karma代码里的package.json文件的peerDependencies属性

 "peerDependencies": {

        "karma-jasmine": "~0.1.0",

        "karma-requirejs": "~0.2.0",

        "karma-coffee-preprocessor": "~0.1.0",

        "karma-html2js-preprocessor": "~0.1.0",

        "karma-chrome-launcher": "~0.1.0",

        "karma-firefox-launcher": "~0.1.0",

        "karma-phantomjs-launcher": "~0.1.0",

        "karma-script-launcher": "~0.1.0"

  }

然后一个典型的运行框架通常都需要一个配置文件,在karma里可以是一个karma.conf.js,里面的代码是一个nodejs风格的,一个普通的例子如下:

module.exports = function(config){

  config.set({

    // 下面files里的基础目录

    basePath : '../',

    // 测试环境需要加载的JS信息

    files : [

      'app/bower_components/angular/angular.js',

      'app/bower_components/angular-route/angular-route.js',

      'app/bower_components/angular-mocks/angular-mocks.js',

      'app/js/**/*.js',

      'test/unit/**/*.js'

    ],

    // 是否自动监听上面文件的改变自动运行测试

    autoWatch : true,

    // 应用的测试框架

    frameworks: ['jasmine'],

    // 用什么环境测试代码,这里是chrome`

    browsers : ['Chrome'],

    // 用到的插件,比如chrome浏览器与jasmine插件

    plugins : [

            'karma-chrome-launcher',

            'karma-firefox-launcher',

            'karma-jasmine',

            'karma-junit-reporter'

            ],

    // 测试内容的输出以及导出用的模块名

    reporters: ['progress', 'junit'],

    // 设置输出测试内容文件的信息

    junitReporter : {

      outputFile: 'test_out/unit.xml',

      suite: 'unit'

    }
  });

};

这里要注意的时,上面的插件大部分都不需要单独安装,因为安装karma的时候已经安装了,这里只有karma-junit-reporter导出插件需要单独安装,想要了解更多的关于配置文件的信息可以,点击这里

karma就讲到这里,想了解更多关于它的信息可以,点击这里

什么是jasmine

Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.

上面是jasmine官方文档里对它的解释,下面用中文简单的翻译下

jasmine是一个行为驱动开发的测试框架,不依赖任何js框架以及dom,是一个非常干净以及友好API的测试库.

下面简单的以一个例子来说明它的用法

定义一个测试文件命令为test.js

describe("A spec (with setup and tear-down)", function() {

  var foo;
  beforeEach(function() {

    foo = 0;

    foo += 1;

  });
  afterEach(function() {

    foo = 0;

  });
  it("is just a function, so it can contain any code", function() {

    expect(foo).toEqual(1);

  });
  it("can have more than one expectation", function() {

    expect(foo).toEqual(1);

    expect(true).toEqual(true);

  });

});

上面的例子来自于官网,这里只说下几个重要的API,更多的用法请,点击这里

1.首先任何一个测试用例以describe函数来定义,它有两参数,第一个用来描述测试大体的中心内容,第二个参数是一个函数,里面写一些真实的测试代码

2.it是用来定义单个具体测试任务,也有两个参数,第一个用来描述测试内容,第二个参数是一个函数,里面存放一些测试方法

3.expect主要用来计算一个变量或者一个表达式的值,然后用来跟期望的值比较或者做一些其它的事件

4.beforeEach与afterEach主要是用来在执行测试任务之前和之后做一些事情,上面的例子就是在执行之前改变变量的值,然后在执行完成之后重置变量的值

最后要说的是,describe函数里的作用域跟普通JS一样都是可以在里面的子函数里访问的,就像上面的it访问foo变量

想要运行上面的测试例子可以通过karar来运行,命令例子如下:

karma start test/karma.conf.js

下面我们重点的说说ng里的控制器,指令,服务模块的单元测试.

NG的单元测试

因为ng本身框架的原因,模块都是通过di来加载以及实例化的,所以为了方便配合jasmine来编写测试脚本,所以官方提供了angular-mock.js的一个测试工具类来提供模块定义,加载,注入等.

下面说说ng-mock里的一些常用方法

1.angular.mock.module 此方法同样在window命名空间下,非常方便调用

module是用来配置inject方法注入的模块信息,参数可以是字符串,函数,对象,可以像下面这样使用

beforeEach(module('myApp.filters'));
beforeEach(module(function($provide) {

      $provide.value('version', 'TEST_VER');

}));

它一般用在beforeEach方法里,因为这个可以确保在执行测试任务的时候,inject方法可以获取到模块配置

1.angular.mock.inject 此方法同样在window命名空间下,非常方便调用

inject是用来注入上面配置好的ng模块,方面在it的测试函数里调用,常见的调用例子如下:

angular.module('myApplicationModule', [])

      .value('mode', 'app')

      .value('version', 'v1.0.1');


  describe('MyApp', function() {
    // You need to load modules that you want to test,

    // it loads only the "ng" module by default.

    beforeEach(module('myApplicationModule'));


    // inject() is used to inject arguments of all given functions

    it('should provide a version', inject(function(mode, version) {

      expect(version).toEqual('v1.0.1');

      expect(mode).toEqual('app');

    }));


    // The inject and module method can also be used inside of the it or beforeEach

    it('should override a version and test the new version is injected', function() {

      // module() takes functions or strings (module aliases)

      module(function($provide) {

        $provide.value('version', 'overridden'); // override version here

      });
      inject(function(version) {

        expect(version).toEqual('overridden');

      });

    });

  });

上面是官方提供的一些inject例子,代码很好看懂,其实inject里面就是利用angular.inject方法创建的一个内置的依赖注入实例,然后里面的模块注入跟普通ng模块里的依赖处理是一样的

简单的介绍完ng-mock之后,下面我们分别以控制器,指令,过滤器来编写一个简单的单元测试.

ng里控制器的单元测试

定义一个简单的控制器

var myApp = angular.module('myApp',[]);
    myApp.controller('MyController', function($scope) {

      $scope.spices = [{"name":"pasilla", "spiciness":"mild"},

                       {"name":"jalapeno", "spiciness":"hot hot hot!"},

                       {"name":"habanero", "spiciness":"LAVA HOT!!"}];

      $scope.spice = "hello feenan!";

});

然后我们编写一个测试脚本

describe('myController function', function() {
  describe('myController', function() {

    var $scope;
    beforeEach(module('myApp'));
    beforeEach(inject(function($rootScope, $controller) {

      $scope = $rootScope.$new();

      $controller('MyController', {$scope: $scope});

    }));
    it('should create "spices" model with 3 spices', function() {

      expect($scope.spices.length).toBe(3);

    });
    it('should set the default value of spice', function() {

      expect($scope.spice).toBe('hello feenan!');

    });

  });
});

上面利用了$rootScope来创建子作用域,然后把这个参数传进控制器的构建方法$controller里去,最终会执行上面的控制器里的方法,然后我们检查子作用域里的数组数量以及字符串变量是否跟期望的值相等.

想要了解更多关于ng里的控制器的信息,可以点击这里

ng里指令的单元测试

定义一个简单的指令

var app = angular.module('myApp', []);
app.directive('aGreatEye', function () {

    return {

        restrict: 'E',

        replace: true,

        template: '<h1>lidless, wreathed in flame, 1 times</h1>'

    };

});

然后我们编写一个简单的测试脚本

describe('Unit testing great quotes', function() {

    var $compile;

    var $rootScope;
    // Load the myApp module, which contains the directive

    beforeEach(module('myApp'));
    // Store references to $rootScope and $compile

    // so they are available to all tests in this describe block

    beforeEach(inject(function(_$compile_, _$rootScope_){

      // The injector unwraps the underscores (_) from around the parameter names when matching

      $compile = _$compile_;

      $rootScope = _$rootScope_;

    }));
    it('Replaces the element with the appropriate content', function() {

        // Compile a piece of HTML containing the directive

        var element = $compile("<a-great-eye></a-great-eye>")($rootScope);

        // fire all the watches, so the scope expression 1 will be evaluated

        $rootScope.$digest();

        // Check that the compiled element contains the templated content

        expect(element.html()).toContain("lidless, wreathed in flame, 2 times");

    });

});

上面的例子来自于官方提供的,最终上面的指令将会这用在html里使用

<a-great-eye></a-great-eye>

测试脚本里首先注入$compile与$rootScope两个服务,一个用来编译html,一个用来创建作用域用,注意这里的_,默认ng里注入的服务前后加上_时,最后会被ng处理掉的,这两个服务保存在内部的两个变量里,方便下面的测试用例能调用到

$compile方法传入原指令html,然后在返回的函数里传入$rootScope,这样就完成了作用域与视图的绑定,最后调用$rootScope.$digest来触发所有监听,保证视图里的模型内容得到更新

然后获取当前指令对应元素的html内容与期望值进行对比.

想要了解更多关于ng里的指令的信息,可以点击这里

ng里的过滤器单元测试

定义一个简单的过滤器

var app = angular.module('myApp', []);

app.filter('interpolate', ['version', function(version) {

    return function(text) {

      return String(text).replace(/\%VERSION\%/mg, version);

    };

  }]);

然后编写一个简单的测试脚本
describe('filter', function() {

  beforeEach(module('myApp'));


  describe('interpolate', function() {
    beforeEach(module(function($provide) {

      $provide.value('version', 'TEST_VER');

    }));


    it('should replace VERSION', inject(function(interpolateFilter) {

      expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after');

    }));

  });

});

上面的代码先配置过滤器模块,然后定义一个version值,因为interpolate依赖这个服务,最后用inject注入interpolate过滤器,注意这里的过滤器后面得加上Filter后缀,最后传入文本内容到过滤器函数里执行,与期望值进行对比.

总结

利用测试来开发NG有很多好处,可以保证模块的稳定性,还有一点就是能够深入的了解ng的内部运行机制,所以建议用ng开发的同学赶紧把测试补上吧!

Javascript 相关文章推荐
从零开始学习jQuery (十一) 实战表单验证与自动完成提示插件
Feb 23 Javascript
20款非常优秀的 jQuery 工具提示插件 推荐
Jul 15 Javascript
jQuery实现的图片分组切换焦点图插件
Jan 06 Javascript
微信支付如何实现内置浏览器的H5页面支付
Sep 25 Javascript
详解JavaScript基于面向对象之创建对象(1)
Dec 10 Javascript
使用jquery如何获取时间
Oct 13 Javascript
原生JavaScript实现的简单省市县三级联动功能示例
May 27 Javascript
JavaScript之iterable_动力节点Java学院整理
Jun 29 Javascript
JS实现的透明度渐变动画效果示例
Apr 28 Javascript
ES6中Promise的使用方法实例总结
Feb 18 Javascript
微信小程序之导航滑块视图容器功能的实现代码(简单两步)
Jun 19 Javascript
React服务端渲染原理解析与实践
Mar 04 Javascript
angularjs指令中的compile与link函数详解
Dec 06 #Javascript
angularjs的一些优化小技巧
Dec 06 #Javascript
JavaScript开发人员的10个关键习惯小结
Dec 05 #Javascript
node.js中RPC(远程过程调用)的实现原理介绍
Dec 05 #Javascript
node.js中实现同步操作的3种实现方法
Dec 05 #Javascript
node.js实现BigPipe详解
Dec 05 #Javascript
js实现点击添加一个input节点
Dec 05 #Javascript
You might like
PHP将回调函数作用到给定数组单元的方法
2014/08/19 PHP
解决Yii2邮件发送结果返回成功,但接收不到邮件的问题
2017/05/23 PHP
PHP结合Ffmpeg快速搭建流媒体服务的实践记录
2018/10/31 PHP
js中的setInterval和setTimeout使用实例
2014/05/09 Javascript
Javascript实现网络监测的方法
2015/07/31 Javascript
JavaScript图片轮播代码分享
2015/07/31 Javascript
基于jquery实现可定制的web在线富文本编辑器附源码下载
2015/11/17 Javascript
简单讲解jQuery中的子元素过滤选择器
2016/04/18 Javascript
深入解析Backbone.js框架的依赖库Underscore.js的作用
2016/05/07 Javascript
jQuery实现元素的插入
2017/02/27 Javascript
微信小程序商城项目之淘宝分类入口(2)
2017/04/17 Javascript
详解如何在angular2中获取节点
2017/11/23 Javascript
原生JavaScript实现的简单放大镜效果示例
2018/02/07 Javascript
自己动手封装一个React Native多级联动
2018/09/19 Javascript
Vue实现远程获取路由与页面刷新导致404错误的解决
2019/01/31 Javascript
Vue商品控件与购物车联动效果的实例代码
2019/07/21 Javascript
Js逆向实现滑动验证码图片还原的示例代码
2020/03/10 Javascript
python网络编程之TCP通信实例和socketserver框架使用例子
2014/04/25 Python
一些Python中的二维数组的操作方法
2015/05/02 Python
Python Paramiko模块的安装与使用详解
2016/11/18 Python
Python实现二维数组输出为图片
2018/04/03 Python
transform python环境快速配置方法
2018/09/27 Python
Python检测数据类型的方法总结
2019/05/20 Python
实现Python3数组旋转的3种算法实例
2020/09/16 Python
Python中免验证跳转到内容页的实例代码
2020/10/23 Python
英国旅行箱包和行李箱购物网站:Travel Luggage & Cabin Bags
2019/08/26 全球购物
Genny意大利官网:意大利高级时装品牌
2020/04/15 全球购物
初级Java程序员面试题
2016/03/03 面试题
如何写好升职自荐信
2014/01/06 职场文书
石油工程专业毕业生求职信
2014/04/13 职场文书
社区反邪教工作方案
2014/06/16 职场文书
企业安全生产目标责任书
2014/07/23 职场文书
安全保卫工作竞聘材料
2014/08/25 职场文书
安全伴我行演讲稿
2014/09/04 职场文书
员工开除通知书
2015/04/25 职场文书
springboot中rabbitmq实现消息可靠性机制详解
2021/09/25 Java/Android