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 相关文章推荐
Javascript面向对象之四 继承
Feb 08 Javascript
基于jquery实现的表格分页实现代码
Jun 21 Javascript
JavaScript获取图片的原始尺寸以宽度为例
May 04 Javascript
JavaScript实现随机替换图片的方法
Apr 16 Javascript
详解Angularjs中的依赖注入
Mar 11 Javascript
浅谈vue的踩坑路
Aug 31 Javascript
用最少的JS代码写出贪吃蛇游戏
Jan 12 Javascript
JS中数组与对象的遍历方法实例小结
Aug 14 Javascript
Angular父子组件通过服务传参的示例方法
Oct 31 Javascript
JavaScript多种图形实现代码实例
Jun 28 Javascript
小程序点餐界面添加购物车左右摆动动画
Sep 23 Javascript
Javascript柯里化实现原理及作用解析
Oct 22 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中for循环语句的几种变型
2007/03/16 PHP
Smarty的配置与高级缓存技术分享
2012/06/05 PHP
基于curl数据采集之正则处理函数get_matches的使用
2013/04/28 PHP
php处理单文件、多文件上传代码分享
2016/08/24 PHP
php5.3/5.4/5.5/5.6/7常见新增特性汇总整理
2020/02/27 PHP
让回调函数 showResponse 也带上参数的代码
2007/08/13 Javascript
浅析offsetLeft,Left,clientLeft之间的区别
2013/11/30 Javascript
用循环或if语句从json中取数据示例
2014/08/18 Javascript
深入理解javascript变量声明
2014/11/20 Javascript
基于Vuejs实现购物车功能
2016/08/02 Javascript
etmvc+jQuery EasyUI+combobox多值操作实现角色授权实例
2016/11/09 Javascript
利用js实现前后台传送Json的示例代码
2018/03/29 Javascript
如何理解Vue的v-model指令的使用方法
2018/07/19 Javascript
Vue项目路由刷新的实现代码
2019/04/17 Javascript
微信小程序使用echarts获取数据并生成折线图
2019/10/16 Javascript
vue实现公共方法抽离
2020/07/31 Javascript
[03:22]DOTA2超级联赛专访单车:找到属于自己的英雄
2013/06/08 DOTA
零基础写python爬虫之抓取百度贴吧代码分享
2014/11/06 Python
详尽讲述用Python的Django框架测试驱动开发的教程
2015/04/22 Python
Python实现的基于优先等级分配糖果问题算法示例
2018/04/25 Python
Python在for循环中更改list值的方法【推荐】
2018/08/17 Python
基于Python数据分析之pandas统计分析
2020/03/03 Python
关于keras.layers.Conv1D的kernel_size参数使用介绍
2020/05/22 Python
Python实现在线批量美颜功能过程解析
2020/06/10 Python
解决keras模型保存h5文件提示无此目录问题
2020/07/01 Python
python Selenium 库的使用技巧
2020/10/16 Python
python re的findall和finditer的区别详解
2020/11/15 Python
html5 Canvas画图教程(6)—canvas里画曲线之arcTo方法
2013/01/09 HTML / CSS
苹果中国官方网站:Apple中国
2016/07/22 全球购物
消防安全责任书
2014/04/14 职场文书
奉献演讲稿范文
2014/05/21 职场文书
2015年青年志愿者协会工作总结
2015/04/27 职场文书
写作技巧:优秀文案必备的3种结构
2019/08/19 职场文书
html5表单的required属性使用
2021/07/07 HTML / CSS
如何使用SQL Server语句创建表
2022/04/12 SQL Server
openGauss数据库JDBC环境连接配置的详细过程(Eclipse)
2022/06/01 Java/Android