说说NG里的单元测试

转自:http://www.ngnice.com/posts/dc4b032b537ae0javascript

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


什么是Karma

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

安装测试相关的npm模块,通常的运行karma的话只须要下面两个npm命令,第二个是一个测试框架,karma默认的。java

  • npm install -g karma
  • npm install -g karma-jasmine

   安装karma的时候也会自动的安装一些经常使用的模块node

而后一个典型的运行框架一般都须要一个配置文件,在karma里能够是一个karma.conf.js,里面的代码是一个nodejs风格的,一个普通的例子以下

karmakarma.conf.jsnodejs
// Karma configuration
// Generated on Wed Apr 08 2015 09:26:37 GMT+0800 (中国标准时间)
/*global module:true*/
(function () {
    'use strict';
    module.exports = function (config) {
        config.set({
            basePath: '',
            frameworks: ['jasmine'],
            files: [
                "lib/angular.js",
                "lib/*.js",
                "js/*.js",
                "test/*.js"
            ],
            exclude: [
                "lib/angular-scenario.js"
            ],
            urlRoot: "/base",
            preprocessors: {},
            reporters: ['progress'],
            port: 9876,
            colors: true,
            logLevel: config.LOG_INFO,
            autoWatch: true,
            browsers: ['Chrome'],
            singleRun: false
        });
    };
}());

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

什么是jasmine

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

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

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

(function () {
    "use strict";
    describe("A suite of basic functions", function () {
        it("contains spec with an expectation", function () {
            expect(true).toBe(true);
        });
        var a;
        it("and so is a spec", function () {
            a = true;
            expect(a).toBe(true);
        });
    });
}());
 

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

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

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

  • expect主要用来计算一个变量或者一个表达式的值,而后用来跟指望的值比较或者作一些其它的事件

  • beforeEachafterEach主要是用来在执行测试任务以前和以后作一些事情,上面的例子就是在执行以前改变变量的值,而后在执行完成以后重置变量的值

最后要说的是,describe函数里的做用域跟普通JS同样都是能够在里面的子函数里访问的

想要运行上面的测试例子能够经过karar来运行,命令例子以下

karma start karma.conf.js

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

NG的单元测试

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

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

  • angular.mock.module 此方法一样在window命名空间下,很是方便调用

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

beforeEach(module('myApp.filters')); beforeEach(module(function($provide) { $provide.value('version', 'TEST_VER'); })); 

它通常用在beforeEach方法里,由于这个能够确保在执行测试任务的时候,inject方法能够获取到模块配置

  • angular.mock.inject 此方法一样在window命名空间下,很是方便调用

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

describe("ng-mock test", function () {
        //提早加载模块
        beforeEach(module('test'));

        //测试value,function中的参数是自动注入
        it('should provide a version', inject(function (author, version) {
            expect(version).toEqual('1.0.0');
            expect(author).toEqual('wwy');
        }));
        //用$provide修改value值
        it('should override a version and test the new version is injected', function () {
            module(function ($provide) {
                $provide.value('version', 'overridden');
            });
            inject(function (version) {
                expect(version).toEqual('overridden');
            });
        });
    });
 

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

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

定义一个简单的控制器

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里的控制器的信息,能够点击这里

定义一个简单的指令

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

    app.directive('aGreatEye', function () {
        return {
            restrict: 'E',
            replace: true,
            template: '<h1>lidless, wreathed in flame, {{1 + 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 + 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里的指令的信息,能够点击这里

定义一个简单的过滤器

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开发的同窗赶忙把测试补上吧!

相关文章
相关标签/搜索