Angular.Js的自动化测试详解_AngularJS

本文着重介绍关于ng的测试部分,主要包括以下三个方面:

  1. 框架的选择(Karma+Jasmine)
  2. 测试的分类和选择(单元测试 + 端到端测试)
  3. 在ng中各个模块如何编写测试用例

下面各部分进行详细介绍。

测试的分类

在测试中,一般分为单元测试和端到端测试,单元测试是保证开发者验证代码某部分有效性的技术,端到端(E2E)是当你想确保一堆组件能按事先预想的方式运行起来的时候使用。

其中单元测试又分为两类: TDD(测试驱动开发)和BDD(行为驱动开发)。

下面着重介绍两种开发模式。

TDD(测试驱动开发 Test-driven development)是使用测试案例等来驱动你的软件开发。

如果我们想要更深入点了解TDD,我们可以将它分成五个不同的阶段:

  1.      首先,开发人员编写一些测试方法。
  2.      其次,开发人员使用这些测试,但是很明显的,测试都没有通过,原因是还没有编写这些功能的代码来实际执行。
  3.      接下来,开发人员实现测试中的代码。
  4.      如果开发人员写代码很优秀,那么在下一阶段会看到他的测试通过。
  5.      然后开发人员可以重构自己的代码,添加注释,使其变得整洁,开发人员知道,如果新添加的代码破坏了什么,那么测试会提醒他失败。

其中的流程图如下:


TDD

TDD的好处:

  1.      能驱使系统最终的实现代码,都可以被测试代码所覆盖到,也即“每一行代码都可测”。
  2.      测试代码作为实现代码的正确导向,最终演变为正确系统的行为,能让整个开发过程更加高效。

BDD是(行为驱动开发 Behavior-Driven Development)指的是不应该针对代码的实现细节写测试,而是要针对行为写测试。BDD测试的是行为,即软件应该怎样运行。

  1.      和TDD比起来,BDD是需要我们先写行为规范(功能明细),在进行软件开发。功能明细和测试看起来非常相似,但是功能明细更加含蓄一些。BDD采用了更详细的方式使得它看起来就像是一句话。
  2.      BDD测试应该注重功能而不是实际的结果。你常常会听说BDD是帮助设计软件,而不是像TDD那样的测试软件。

最后总结:TDD的迭代反复验证是敏捷开发的保障,但没有明确如何根据设计产生测试,并保障测试用例的质量,而BDD倡导大家都用简洁的自然语言描述系统行为的理念,恰好弥补了测试用例(即系统行为)的准确性。

测试框架选择

利用karma和jasmine来进行ng模块的单元测试。

     Karma:是一个基于Node.js的JavaScript测试执行过程管理工具,这个测试工具的一个强大特性就是,它可以监控(Watch)文件的变化,然后自行执行,通过console.log显示测试结果。

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

Karma

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

Karma从头开始构建,免去了设置测试的负担,集中精力在应用逻辑上。会产生一个浏览器实例,针对不同浏览器运行测试,同时可以对测试的运行进行一个实时反馈,提供一份debug报告。

测试还会依赖一些Karma插件,如测试覆盖率Karma-coverage工具、Karman-fixture工具及Karma-coffee处理工具。此外,前端社区里提供里比较丰富的插件,常见的测试需求都能涵盖到。

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

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

然后一个典型的运行框架通常都需要一个配置文件,在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 start test/karma.conf.js

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);
 });
});
  1.      首先任何一个测试用例以describe函数来定义,它有两参数,第一个用来描述测试大体的中心内容,第二个参数是一个函数,里面写一些真实的测试代码
  2.      it是用来定义单个具体测试任务,也有两个参数,第一个用来描述测试内容,第二个参数是一个函数,里面存放一些测试方法
  3.      expect主要用来计算一个变量或者一个表达式的值,然后用来跟期望的值比较或者做一些其它的事件
  4.      beforeEach与afterEach主要是用来在执行测试任务之前和之后做一些事情,上面的例子就是在执行之前改变变量的值,然后在执行完成之后重置变量的值

开始单元测试

下面分别以控制器,指令,过滤器和服务四个部分来编写相关的单元测试。项目地址为angular-seed(点我)项目,可以下载demo并运行其测试用例。

demo中是一个简单的todo应用,会包含一个文本输入框,其中可以编写一些笔记,按下按钮可以将新的笔记加入笔记列表中,其中使用notesfactory封装LocalStorage来储存笔记信息。

先介绍一下angular中测试相关的组件angular-mocks。

了解angular-mocks

在Angular中,模块都是通过依赖注入来加载和实例化的,因此官方提供了angular-mocks.js测试工具来提供模块的定义、加载,依赖注入等功能。

其中一些常用的方法(挂载在window命名空间下):

angular.mock.module: module用来加载已有的模块,以及配置inject方法注入的模块信息。具体使用如下:

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

该方法一般在beforeEach中使用,在执行测试用例之前可以获得模块的配置。

angular.mock.inject: inject用来注入配置好的ng模块,来供测试用例里进行调用。具体使用如下:

it('should provide a version', inject(function(mode, version) {
 expect(version).toEqual('v1.0.1');
 expect(mode).toEqual('app');
 }));

其实inject里面就是利用angular.inject方法创建的一个内置的依赖注入实例,然后里面的模块和普通的ng模块的依赖处理是一样的。

Controller部分

Angular模块是todoApp,控制器是TodoController,当按钮被点击时,TodoController的createNote()函数会被调用。下面是app.js的代码部分。

var todoApp = angular.module('todoApp',[]);
todoApp.controller('TodoController',function($scope,notesFactory){
 $scope.notes = notesFactory.get();
 $scope.createNote = function(){
 notesFactory.put($scope.note);
 $scope.note='';
 $scope.notes = notesFactory.get();
 }
});
todoApp.factory('notesFactory',function(){
 return {
 put: function(note){
 localStorage.setItem('todo' + (Object.keys(localStorage).length + 1), note);
 },
 get: function(){
 var notes = [];
 var keys = Object.keys(localStorage);
 for(var i = 0; i < keys.length; i++){
  notes.push(localStorage.getItem(keys[i]));
 }
 return notes;
 }
 };
});

在todoController中用了个叫做notesFactory的服务来存储和提取笔记。当createNote()被调用时,会使用这个服务将一条信息存入LocalStorage中,然后清空当前的note。因此,在编写测试模块是,应该保证控制器初始化,scope中有一定数量的笔记,在调用createNote()之后,笔记的数量应该加一。

具体的单元测试如下:

describe('TodoController Test', function() {
 beforeEach(module('todoApp')); // 将会在所有的it()之前运行
 // 我们在这里不需要真正的factory。因此我们使用一个假的factory。
 var mockService = {
 notes: ['note1', 'note2'], //仅仅初始化两个项目
 get: function() {
 return this.notes;
 },
 put: function(content) {
 this.notes.push(content);
 }
 };
 // 现在是真正的东西,测试spec
 it('should return notes array with two elements initially and then add one',
 inject(function($rootScope, $controller) { //注入依赖项目
 var scope = $rootScope.$new();
 // 在创建控制器的时候,我们也要注入依赖项目
 var ctrl = $controller('TodoController', {$scope: scope, notesFactory:mockService});
 // 初始化的技术应该是2
 expect(scope.notes.length).toBe(2);
 // 输入一个新项目
 scope.note = 'test3';
 // now run the function that adds a new note (the result of hitting the button in HTML)
 // 现在运行这个函数,它将会增加一个新的笔记项目
 scope.createNote();
 // 期待现在的笔记数目是3
 expect(scope.notes.length).toBe(3);
 })
 );
});

在beforeEach中,每一个测试用例被执行之前,都需要加载模块module("todoApp")

由于不需要外部以来,因此我们本地建立一个假的mockService来代替factory,用来模拟noteFactory,其中包含相同的函数,get()put() 。这个假的factory从数组中加载数据代替localStorage的操作。

在it中,声明了依赖项目$rootScope$controller,都可以由Angular自动注入,其中$rootScope用来获得根作用域,$controller用作创建新的控制器。

$controller服务需要两个参数。第一个参数是将要创建的控制器的名称。第二个参数是一个代表控制器依赖项目的对象,
$rootScope.$new()方法将会返回一个新的作用域,它用来注入控制器。同时我们传入mockService作为假factory。
之后,初始化会根据notes数组的长度预测笔记的数量,同时在执行了createNote()函数之后,会改变数组的长度,因此可以写出两个测试用例。

Factory部分

factory部分的单元测试代码如下:

describe('notesFactory tests', function() {
 var factory;
 // 在所有it()函数之前运行
 beforeEach(function() {
 // 载入模块
 module('todoApp');
 // 注入你的factory服务
 inject(function(notesFactory) {
 factory = notesFactory;
 });
 var store = {
 todo1: 'test1',
 todo2: 'test2',
 todo3: 'test3'
 };
 spyOn(localStorage, 'getItem').andCallFake(function(key) {
 return store[key];
 });
 spyOn(localStorage, 'setItem').andCallFake(function(key, value) {
 return store[key] = value + '';
 });
 spyOn(localStorage, 'clear').andCallFake(function() {
 store = {};
 });
 spyOn(Object, 'keys').andCallFake(function(value) {
 var keys=[];
 for(var key in store) {
 keys.push(key);
 }
 return keys;
 });
 });
 // 检查是否有我们想要的函数
 it('should have a get function', function() {
 expect(angular.isFunction(factory.get)).toBe(true);
 expect(angular.isFunction(factory.put)).toBe(true);
 });
 // 检查是否返回3条记录
 it('should return three todo notes initially', function() {
 var result = factory.get();
 expect(result.length).toBe(3);
 });
 // 检查是否添加了一条新纪录
 it('should return four todo notes after adding one more', function() {
 factory.put('Angular is awesome');
 var result = factory.get();
 expect(result.length).toBe(4);
 });
});

在TodoController模块中,实际上的factory会调用localStorage来存储和提取笔记的项目,但由于我们单元测试中,不需要依赖外部服务去获取和存储数据,因此我们要对localStorage.getItem()localStorage.setItem()进行spy操作,也就是利用假函数来代替这两个部分。

spyOn(localStorage,'setItem')andCallFake()是用来用假函数进行监听的。第一个参数指定需要监听的对象,第二个参数指定需要监听的函数,然后andCallfake这个API可以编写自己的函数。因此,测试中完成了对localStorage和Object的改写,使函数可以返回我们自己数组中的值。

在测试用例中,首先检测新封装的factory函数是否包含了get()put()这两个方法,,然后进行factory.put()操作后断言笔记的数量。

Filter部分

我们添加一个过滤器。truncate的作用是如果传入字符串过长后截取前10位。源码如下:

todoApp.filter('truncate',function(){
 return function(input,length){
 return (input.length > length ? input.substring(0,length) : input);
 }
});

所以在单元测试中,可以根据传入字符串的情况断言生成子串的长度。

describe('filter test',function(){
 beforeEach(module('todoApp'));
 it('should truncate the input to 1o characters',inject(function(truncateFilter){
 expect(truncateFilter('abcdefghijkl',10).length).toBe(10);
 });
 );
});

之前已经对断言进行讨论了,值得注意的一点是我们需要在调用过滤器的时候在名称后面加入Filter,然后正常调用即可。

Directive部分

源码中的指令部分:

todoApp.directive('customColor', function() {
 return {
 restrict: 'A',
 link: function(scope, elem, attrs) {
 elem.css({'background-color': attrs.customColor});
 }
 };
});

由于指令必须编译之后才能生成相关的模板,因此我们要引入$compile服务来完成实际的编译,然后再测试我们想要进行测试的元素。

angular.element()会创建一个jqLite元素,然后我们将其编译到一个新生成的自作用域中,就可以被测试了。具体测试用例如下:

describe('directive tests',function(){
 beforeEach(module('todoApp'));
 it('should set background to rgb(128, 128, 128)',
 inject(function($compile,$rootScope) {
 scope = $rootScope.$new();
 // 获得一个元素
 elem = angular.element("<span custom-color=\"rgb(128, 128, 128)\">sample</span>");
 // 创建一个新的自作用域
 scope = $rootScope.$new();
 // 最后编译HTML
 $compile(elem)(scope);
 // 希望元素的背景色和我们所想的一样
 expect(elem.css("background-color")).toEqual('rgb(128, 128, 128)');
 })
 );
});

开始端到端测试

在端到端测试中,我们需要从用户的角度出发,来进行黑盒测试,因此会涉及到一些DOM操作。将一对组件组合起来然后检查是否如预想的结果一样。
在这个demo中,我们模拟用户输入信息并按下按钮的过程,检测信息能否被添加到localStorage中。

在E2E测试中,需要引入angular-scenario这个文件,并且建立一个html作为运行report的展示,在html中包含带有e2e测试代码的执行js文件,在编写完测试之后,运行该html文件查看结果。具体的e2e代码如下:

describe('my app', function() {
 beforeEach(function() {
 browser().navigateTo('../../app/notes.html');
 });
 var oldCount = -1;
 it("entering note and performing click", function() {
 element('ul').query(function($el, done) {
 oldCount = $el.children().length;
 done();
 });
 input('note').enter('test data');
 element('button').query(function($el, done) {
 $el.click();
 done();
 });
 });
 it('should add one more element now', function() {
 expect(repeater('ul li').count()).toBe(oldCount + 1);
 });
});

我们在端到端测试过程中,首先导航到我们的主html页面app/notes.html,可以通过browser.navigateTo()来完成,element.query()函数选择了ul元素并记录其中有多少个初始化的项目,存放在oldCount变量中。

然后通过input('note').enter()来键入一个新的笔记,然后模拟一下点击操作来检查是否增加了一个新的笔记(li元素)。然后通过断言可以将新旧的笔记数进行对比。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索自动化测试
angular自动化测试
angularjs自动化测试、angularjs 指令详解、angularjs q详解、angularjs mvc 详解、angularjs event详解,以便于您获取更多的相关知识。

时间: 2024-08-30 02:43:41

Angular.Js的自动化测试详解_AngularJS的相关文章

flash js Fusioncharts 参数详解与参考

flash js fusioncharts 参数详解与参考 objects anchors 锚点 用于标识line或area的数值点 支持效果 animation 动画.shadow 阴影.glow 发光.bevel 倾斜.blur 模糊 动画属性 _alpha._x._y._xscale._yscale background 整个图表的背景 支持属性 animation.shadow.glow.bevel.blur 动画属性 _alpha objects anchors 锚点 用于标识line

JS hashMap实例详解_javascript技巧

Hashmap是一种非常常用的.应用广泛的数据类型.本文通过实例代码给大家介绍js hashMap的相关知识,具体代码内容如下所示: /** * MAP对象,实现MAP功能 * * 接口: * size() 获取MAP元素个数 * isEmpty() 判断MAP是否为空 * clear() 删除MAP所有元素 * put(key, value) 向MAP中增加元素(key, value) * remove(key) 删除指定KEY的元素,成功返回True,失败返回False * get(key)

Node.js返回JSONP详解_node.js

在使用JQuery的Ajax从服务器请求数据或者向服务器发送数据时常常会遇到跨域无法请求的错误,常用的解决办法就是在Ajax中使用JSONP.基于安全性考虑,浏览器会存在同源策略,然而<script/>标签却具有跨域访问数据的能力,这就是JSONP工作的基本原理.有关同源策略以及什么是JSONP. 在Node.js中实现JSONP非常简单,通过下面的代码我们从服务器返回并运行一个JavaScript函数,这个JavaScript函数已经在调用方提前被定义好了,于是当它被返回的时候就自动执行了.

Angular.js实现注册系统的实例详解_AngularJS

前言 相信大家在做项目里经常需要登录注册,那么在用angularjs该如何实现.下面让我们通过angualr.js来实现注册系统表单验证. Angular下载地址:https://code.angularjs.org/1.5.0/angular.js 首先看一下页面效果(通过bootstrap实现的布局样式):   当我们点击提交按钮时,会根据表单验证,若通过,则没有提示语句,若不通过,则会弹出响应提示语句,当然该功能可以通过其他简单方式实现,这里只是通过实战对angular进一步深入理解. 实

Angular中$cacheFactory的作用和用法实例详解_AngularJS

先说下缓存: 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能够服务的请求越多,整体系统性能就能提升越多. $cacheFactory介绍: $cacheFactory是一个为Angular服务生产缓存对象的服务.要创建一个缓存对象,可以使用$cacheFactory通过一个ID和capacity.其中,ID是一个缓存对象的名称,capacity则是描述缓存键值对的最大数量. 1.

Angular表单验证实例详解_AngularJS

表单验证 我去,我感觉我这个人其实还是一个很傻逼的一个人,老是因为拼错了一个单词或者怎么样就浪费我很长时间,这样真的不行不行,要正确对待这个问题,好了,说正题吧,angular也有表单验证minlength,maxlength,required呀这些个东西,还有也支持h5的那些验证,h5的那些验证,就是type啦,type='email',number,url呀这些,然后现在要用angular来验证,可以定义样式哈,不错,然后怎么验证呢,好的上代码 <!DOCTYPE html> <ht

Angular 中 select指令用法详解_AngularJS

最近在angular中使用select指令时,出现了很多问题,搞得很郁闷.查看了很多资料后,发现select指令并不简单,决定总结一下. select用法: <select ng-model="" [name=""] [required=""] [ng-required=""] [ng-options=""]> </select> 属性说明: 发现并没有ng-change属性 ng-

AngularJS控制器之间的通信方式详解_AngularJS

本文实例讲述了AngularJS控制器之间的通信方式.分享给大家供大家参考,具体如下: 一.利用作用域的继承方式 由于作用域的继承是基于js的原型继承方式,所以这里分为两种情况,当作用域上面的值为基本类型的时候,修改父作用域上面的值会影响到子作用域,反之,修改子作用域只会影响子作用域的值,不会影响父作用域上面的值:如果需要父作用域与子作用域共享一个值的话,就需要用到后面一种,即作用域上的值为对象,任何一方的修改都能影响另一方,这是因为在js中对象都是引用类型. 基本类型 function San

AngularJS创建自定义指令的方法详解_AngularJS

本文实例讲述了AngularJS创建自定义指令的方法.分享给大家供大家参考,具体如下: 这是一篇译文,来自angular开发者说明的指令.主要面向已经熟悉angular开发基础的开发者.这篇文档解释了什么情况下需要创建自己的指令,和如何去创建指令. 什么是指令 从一个高的层面来讲,指令是angular $compile服务的说明,当特定的标签(属性,元素名,或者注释) 出现在DOM中的时候,它让编译器附加指定的行为到DOM上. 这个过程是很简单的.angular内部有很用这样自带的指令,比如说n