angularjs中的单元测试实例

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

时间: 2014-12-04

AngularJS 在同一个界面启动多个ng-app应用模块详解

AngularJS默认在一个html界面中只启动一个 ng-app 模块,而且是界面中第一次出现的那个使用 ng-app 声明的模块,该问题可以通过查看AngularJS源代码或者是文档验证. 解决方案: 直接上代码,如果有兴趣了解其中缘由,可以选择阅读后面的部分: <!DOCTYPE html> <html> <head lang="zh_CN"> <meta charset="UTF-8"> <title&g

jQuery和AngularJS的区别浅析

最近一直在研究angularjs,最大的感受就是它和之前的jQuery以及基于jQuery的各种库设计理念完全不同,如果不能认识到这点而对于之前做jQuery开发的程序员,去直接学习angularjs的话,很可能学了很久还不知道这个东西能用来干什么以及怎么使用,怎么和UI进行结合等问题,在stackoverflow上找到一篇关于这方面的文章,阅读之后颇有收获,在此基础上将它译成中文,以求抛砖引玉大家一同学习. 原问题:假如我熟悉利用jQuery去开发客户端应用,那么我怎么上手angularjs,

angularJS中router的使用指南

这几天看了angularjs和backbone,大看了解了knockout和emberjs,刚刚上网看到了一个angular的router的demo,现在顺便记下来 复制代码 代码如下: <!--- DEMO_INDEX.html --> <!doctype html> <head> <meta charset="utf-8"> <title>route</title> </head><br>

使用AngularJS来实现HTML页面嵌套的方法

HTML不支持嵌入在HTML页面中的HTML页面.实现这一功能通过使用以下方式: 使用Ajax - 让一台服务器来调用获取相应的HTML页面,并将其设置在HTML控件的innerHTML. 使用服务器端包含 - JSP,PHP等Web端服务器技术可以在包括动态页面中的HTML页面. 使用AngularJS,我们可以用ng-include指令在一个HTML页面嵌入另一个HTML页面. <div ng-app="" ng-controller="studentControl

Angularjs制作简单的路由功能demo

从官网下载了最新版本的Angularjs 版本号:1.3.15 做一个简单的路由功能demo 首页:index.html <!DOCTYPE html > <html> <head> <meta charset="utf-8" /> <title>测试</title> <script src="./js/angular.min.js"></script> <scri

angularjs 处理多个异步请求方法汇总

在实际业务中经常需要等待几个请求完成后再进行下一步操作.但angularjs中$http不支持同步的请求. 解决方法一: 复制代码 代码如下: $http.get('url1').success(function (d1) {         $http.get('url2').success(function (d2) {             //处理逻辑         });     }); 解决方法二: then中的方法会按顺序执行. 复制代码 代码如下: var app = ang

学习AngularJs:Directive指令用法(完整版)

本教程使用AngularJs版本:1.5.3 AngularJs GitHub: https://github.com/angular/angular.js/ AngularJs下载地址:https://angularjs.org/ 摘要:Directive(指令)笔者认为是AngularJ非常强大而有有用的功能之一.它就相当于为我们写了公共的自定义DOM元素或CLASS属性或ATTR属性,并且它不只是单单如此,你还可以在它的基础上来操作scope.绑定事件.更改样式等.通过这个Directiv

AngularJS中的模块详解

在讲angularjs的模块之前,我们先介绍一下angular的一些知识点: AngularJS是纯客户端技术,完全用Javascript编写的.它使用的是网页开发的常规技术(HTML,CSS,Javascript),目的是让网页应用开发更快更容易. AngularJS简化应用开发的一个重要方法是,将一个些通用的低级开发操作包装起来提供给开发者.AngularJS会自动处理好这些低级操作.它们包括: 1.DOM操作 2.设置事件的监听 3.输入验证,因为AngularJS会处理大部分这些操作,所

AngularJS入门教程之学习环境搭建

学习AngularJS的一个好方法是逐步完成本教程,它将引导您构建一个完整的AngularJS web应用程序. 该web应用是一个Android设备清单的目录列表,您可以筛选列表以便查看您感兴趣的设备,然后查看设备的详细信息. 本教程将向您展示AngularJS怎样使得web应用更智能更灵活,而且不需要各种扩展程序或插件. 通过本教程的学习,您将: 1.阅读示例学习怎样使用AngularJS的客户端数据绑定和依赖注入功能来建立可立即响应用户操作的动态数据视图. 2.学习如何使用AngularJ

AngularJS ng-app 指令实例详解

AngularJS ng-app 指令 AngularJS 实例 让 body 元素称为 AngularJS 应用的根元素: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>

AngularJs表单验证实例详解

常用的表单验证指令 1. 必填项验证 某个表单输入是否已填写,只要在输入字段元素上添加HTML5标记required即可: 复制代码 代码如下: <input type="text" required /> 2. 最小长度 验证表单输入的文本长度是否大于某个最小值,在输入字段上使用指令ng-minleng= "{number}": 复制代码 代码如下: <input type="text" ng-minlength="

AngularJS封装$http.post()实例详解

  AngularJS封装$http.post()实例详解 用了不是很长的时间跟了一个移动APP项目,用的是ionic + AngularJS + cordova框架,其间遇到过挺多问题,其中一个就是对Ajax的封装问题. 其实针对封装问题一直以来就没停止过谈论,不同的项目也有不同的需求,举个典型的例子,我在做这个项目的时候因为一开始没有考虑封装问题所以被批评了,而我的一个朋友却因为封装了受到了批评--很尴尬不是么. 那么对于是否要封装这个问题,究竟该怎么界定?其实这不是一个很复杂的问题,归根到

Angularjs中数据绑定的实例详解

Angularjs中数据绑定的实例详解 这是一个最简单的angularjs的例子,关于数据绑定的,大家可以执行一下,看看效果 <html ng-app> <head> <title>angularjs-include</title> <script type="text/javascript" src="js/angular/angular.min.js"></script> </head

Angular1.x复杂指令实例详解

本文实例讲述了Angular1.x复杂指令.分享给大家供大家参考,具体如下: 名称 描述 compile 指定一个编译函数 controller 为指令创建一个控制器函数 link 为指令指定链接函数 replace 指定模板内容是否替换指令所应用到的元素 require 声明对某个控制器的依赖 restrict 指定指令如何使用ACEM scope 为指令创建一个新的作用域或者一个隔离的作用域 template 指定一个将被插入到HTML文档的模板 templateUrl 指定一个将被插入到H

Vue的土著指令和自定义指令实例详解

1.土著指令 当我开始学习Vue的时候,看官网的时候看到了"指令"两个字.我愣住了,what?指令是啥啊?后来继续往下看,像这种什么"v-for""v-show""v-if"都叫做指令.等到后来Vue玩的差不多了,开始写项目的时候发现,常见的指令也就那么几个,比如"v-if""v-show""v-model""v-for""v-bind&

AngularJS表单提交实例详解

本文实例讲述了AngularJS表单提交.分享给大家供大家参考,具体如下: AngularJS中的数据绑定 AngularJS创建实时模板来代替视图,而不是将数据合并进模板之后更新DOM.任何一个独立视图组件中的值都是动态替换的. ng-app属性声明所有被其包含的内容都属于这个AngularJS应用,这也是我们可以在Web应用中嵌套AngularJS应用的原因.只有被具有ng-app属性的DOM元素包含的元素才会受AngularJS影响. 当AngularJS认为某个值可能发生变化时,它会运行

Angular1.x自定义指令实例详解

本文实例讲述了Angular1.x自定义指令.分享给大家供大家参考,具体如下: 调用Module.directive方法,传入指令名称和工厂函数,返回一个对象. 指令名称中每个大写字母会被认为是属性名中的一个独立的词,而每个词之间是以一个连字符分隔的. var myApp = angular.module('myApp', []) .directive("unorderedList", function () { return function(scope, element, attr

Vue学习之常用指令实例详解

本文实例讲述了Vue常用指令.分享给大家供大家参考,具体如下: 1.创建一个vue实例 vue的一个特点就是数据驱动界面,一旦对js中的数据进行修改,界面中用到数据的地方也会立马做出更改.为了对界面进行操纵,需要先获取到界面的标签元素,并实例化一个vue实例. 例如在HTML创建一个div: <div id="app"> <p>{{msg}}</p> </div> 在js中将其实例化为vue对象: let app=new Vue({ el

Android应用App更新实例详解

前言:现在一般的Android软件都是需要不断更新的,当你打开某个app的时候,如果有新的版本,它会提示你有新版本需要更新.该项目实现的就是这个功能.并且有强制更新和更新提示两种方式,当有更新时,会弹出一个提示框,点击下载,则在通知来创建一个进度条进行下载,点击取消,则取消更新. 效果: 开发环境:AndroidStudio2.1.2+gradle-2.10 部分代码: public class UpdateVersionController { private Context context;