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为textarea添加maxlength属性并且兼容IE
Apr 25 Javascript
js的for in循环和java里foreach循环的区别分析
Jan 28 Javascript
利用JavaScript的AngularJS库制作电子名片的方法
Jun 18 Javascript
javascript实现列表切换效果
May 02 Javascript
JavaScript_object基础入门(必看篇)
Jun 13 Javascript
jQuery密码强度验证控件使用详解
Jan 05 Javascript
canvas实现探照灯效果
Feb 07 Javascript
js+html5实现页面可刷新的倒计时效果
Jul 15 Javascript
jQuery中将json数据显示到页面表格的方法
May 27 jQuery
使用 Node.js 实现图片的动态裁切及算法实例代码详解
Sep 29 Javascript
layui添加动态菜单与选项卡
Jul 26 Javascript
稍微学一下Vue的数据响应式(Vue2及Vue3区别)
Nov 21 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过滤危险html代码
2008/08/18 PHP
PHP 获取文件路径(灵活应用__FILE__)
2013/02/15 PHP
mantis安装、配置和使用中的问题小结
2014/07/14 PHP
浅析Yii2中GridView常见操作
2016/04/22 PHP
一个js封装的不错的选项卡效果代码
2008/02/15 Javascript
JavaScript操作XML 使用百度RSS作为新闻源示例
2012/02/17 Javascript
extjs关于treePanel+chekBox全部选中以及清空选中问题探讨
2013/04/02 Javascript
js改变文章字体大小的实例代码
2013/11/27 Javascript
php实例分享之实现显示网站运行时间
2014/05/20 Javascript
JS+DIV+CSS实现的经典标签切换效果代码
2015/09/14 Javascript
基于jquery实现可定制的web在线富文本编辑器附源码下载
2015/11/17 Javascript
JavaScript为事件句柄绑定监听函数实例详解
2015/12/15 Javascript
jqueryMobile 动态添加元素,展示刷新视图的实现方法
2016/05/28 Javascript
ES6新特征数字、数组、字符串
2016/10/01 Javascript
canvas快速绘制圆形、三角形、矩形、多边形方法介绍
2016/12/29 Javascript
JavaScript实现分页效果
2017/03/28 Javascript
jQuery实现鼠标响应式淘宝动画效果示例
2018/02/13 jQuery
vue2.0使用swiper组件实现轮播的示例代码
2018/03/03 Javascript
Echarts动态加载多条折线图的实现代码
2019/05/24 Javascript
微信小程序顶部导航栏可滑动并选中放大
2019/12/05 Javascript
JavaScript实现飞舞的泡泡效果
2020/02/07 Javascript
JavaScript通如何过RGraph实现动态仪表盘
2020/10/15 Javascript
Node.js 中如何收集和解析命令行参数
2021/01/08 Javascript
Python高级应用实例对比:高效计算大文件中的最长行的长度
2014/06/08 Python
跟老齐学Python之用Python计算
2014/09/12 Python
python多线程方式执行多个bat代码
2016/06/07 Python
Python遍历文件夹和读写文件的实现方法
2017/05/10 Python
Python为何不能用可变对象作为默认参数的值
2019/07/01 Python
解决使用export_graphviz可视化树报错的问题
2019/08/09 Python
使用Python进行中文繁简转换的实现代码
2019/10/18 Python
解决virtualenv -p python3 venv报错的问题
2021/02/05 Python
Maxpeedingrods美国:高性能汽车零件
2020/02/14 全球购物
编程实现去掉XML的重复结点
2014/05/28 面试题
会计专业大学生职业生涯规划范文
2014/01/11 职场文书
党员公开承诺书和承诺事项
2014/03/25 职场文书
《风筝》教学反思
2014/04/10 职场文书