AngularJS测试
一 测试工具
1.NodeJS领域:Jasmine做单元测试,Karma自动化完成单元测试,Grunt启动Karma统一项目管理,Yeoman最后封装成一个项目原型模板,npm做nodejs的包依赖管理,bower做javascript的包依赖管理。Java领域:JUnit做单元测试, Maven自动化单元测试,统一项目管理,构建项目原型模板,包依赖管理。
Nodejs让组合变得更丰富,却又在加重我们的学习门槛。唉......
2.Karma
Karma是一个测试工具,它从头开始构建,免去了设置测试方面的负担,这样我们就可以将主要精力放在构建核心应用逻辑上。
Karma产生一个浏览器实例(或者多个不同的浏览器实例),针对不同的浏览器实例运行测试,检测在不同浏览器环境下测试是否通过。Karma与浏览器通过socket.io来联系,这能让Karma保持持续通信。因此Karma提供了关于哪些测试正在运行的实时反馈,提供一份适合人类阅读的输出,告诉我们哪些测试通过、哪些失败或者超时。
Karma测试运行器同时支持单元测试和端到端测试。
3.Karma安装
如果你已经安装了NodeJS和npm,就可以通过npm命令来安装karma
安装命令:npm install -g karma
测试是否安装成功:karma start
如果成功安装karma,则可以通过浏览器看到karma界面
4.karma+jasmine配置
初始化karma配置文件karma.conf.js:karma init
安装集成包karma-jasmine:npm install karma-jasmine
karma安装好后,后面就是具体的jasmine测试了。
二 jasmine测试
1.尽管Karma支持多种测试框架,但默认的选项是Jasmine。Jasmine是一个用于测试JavaScript代码的行为驱动开发框架。
下载发布的安装包:jasmine-standalone-2.2.0.zip
下载后的目录结构:
(1)lib:存放了运行测试案例所必须的文件,其内包含jasmine-2.2.0文件夹。可以将不同版本的Jasmine放在lib下,以便使用时切换。
jasmine.js:整个框架的核心代码。
jasmine-html.js:用来展示测试结果的js文件。
boot.js:jasmine框架的的启动脚本。需要注意的是,这个脚本应该放在jasmine.js之后,自己的js测试代码之前加载。
jasmine.css:用来美化测试结果。
(2)spec:存放测试脚本。
PlayerSpec.js:就是针对src文件夹下的Player.js所写的测试用例。
SpecHelper.js:用来添加自定义的检验规则,如果框架本身提供的规则(诸如toBe,toNotBe等)不适用,就可以额外添加自己的规则(在本文件中添加了自定义的规则toBePlaying)。
(3)src:存放需要测试的js文件。Jasmine提供了一个Example(Player.js,Song.js)。
(4)pecRunner.html:运行测试用例的环境。它将上面3个文件夹中一些必要的文件都包含了进来。如果你想将自己的测试添加进来的话,那么就修改相应的路径。
2.核心概念
(1)细则套件Suites
Suite表示一个测试集,以函数describe(string, function)封装。describe函数是Jasmine套件定义的一个全局函数,所以可以在测试中直接调用。
describe()函数带有两个参数,一个字符串,一个函数。字符串是待建立的细则(spec)套件名称或者描述,函数封装了测试套件。
可以嵌套这些describe()函数,这样我们可以创建一个测试树来执行那些在测试中设置的不同条件。如:
describe('Unit test: MainController', function() {
describe('index method', function() {
// 细则放这里
});
});
一个Suite(describe)包含多个Specs(it),一个Specs(it)包含多个断言(expect)。
使用describe()函数把相关的细则分组是个不错的主意。在每个describe()块运行时,这些字符串会沿着细则的名称链接起来。因此,上面这个例子的标题就会变成“Unit test:MainController index method.”然后,这些describe()块的标题就会被追加到细则的标题上。设计这个步骤的目的是让我们以完整句子来阅读细则的,所以把测试命名成可读的英文就很重要了。
(2)定义一个细则
Spec表示测试用例,以it(string, function)函数封装。我们通过调用it()函数来定义一个细则。这个函数也是在Jasmine测试套件中定义的全局函数,所以可以从测试中直接调用。
it()函数带有两个参数:一个字符串,是细则的标题或者描述;一个函数,包含了一个或多个用于测试代码功能的预期。
这些预期都是函数,执行时评估为true或false。一个所有预期都为true的测试就算是一条通过的细则,一条细则有一个或者多个预期为false的话,就是个失败的测试。
一个简单的测试可能像这样:
describe('A spec suite', function() {
it('contains a passing spec', function() {
expect(true).toBe(true);
});
});
这个细则的标题,追加到describe()标题之后,就成为了“一个细则套件包含一条已通过的细则”。
(3)预期
测试应用时,我们会想要断言条件在应用的不同阶段是符合我们期望的。我们要写的这个测试读起来就像这样:“如果我们点击这个按钮,就期望有这个结果。”例如,“如果我们导航到首页,我们期望欢迎信息会被渲染出来。”
使用expect()函数来建立预期。expect()函数带有一个单值参数。这个参数被称为真实值。
要建立一个预期,我们给它串联一个带单值参数的匹配器函数,这个参数就是期望值。
这些匹配器函数实现了一个在真实值和期望值之间的布尔比较。可以通过在调用匹配器之前调一个not来创建测试的否定式。
(4)内置匹配器matchers
常用的Matchers有:
    toBe():相当于===比较。
    toNotBe()
    toBeDefined():检查变量或属性是否已声明且赋值。
    toBeUndefined()
    toBeNull():是否是null。
    toBeTruthy():如果转换为布尔值,是否为true。
    toBeFalsy()
    toBeLessThan():数值比较,小于。
    toBeGreaterThan():数值比较,大于。
    toEqual():相当于==,注意与toBe()的区别。
    toNotEqual()
    toContain():数组中是否包含元素(值)。只能用于数组,不能用于对象。
    toBeCloseTo():数值比较时定义精度,先四舍五入后再比较。
    toHaveBeenCalled()
    toHaveBeenCalledWith()
    toMatch():按正则表达式匹配。
    toNotMatch()
    toThrow():检验一个函数是否会抛出一个错误
所有的matchers匹配器支持添加 .not反转结果: expect(x).not.toEqual(y);
举例:
describe('A spec suite', function() {
it('contains a passing spec', function() {
var value = "<h2>Header element: welcome</h2>";
expect(value).toMatch(/welcome/);
expect(value).toMatch('welcome');
expect(value).not.toMatch('goodbye');
});
});
 
describe('A spec suite', function() {
it('contains a passing spec', function() {
var arr = [1,2,3,4];
expect(arr).toContain(4);
expect(arr).not.toContain(12);
});
});
 
describe('A spec suite', function() {
it('contains a passing spec', function() {
expect(function() {
return a + 10;
}).toThrow();
expect(function() {
return 2 + 10;
}).not.toThrow();
});
});
(5)自定义matcher匹配器
自定义Matcher(被称为Matcher Factories)实质上是一个函数(该函数的参数可以为空),该函数返回一个闭包,该闭包的本质是一个compare函数,compare函数接受2个参数:actual value 和 expected value。
compare函数必须返回一个带pass属性的结果Object,pass属性是一个Boolean值,表示该Matcher的结果(为true表示该Matcher实际值与预期值匹配,为false表示不匹配),也就是说,实际值与预期值具体的比较操作的结果,存放于pass属性中。
(6)Setup和Teardown操作
Jasmine的Setup和Teardown操作(Setup在每个测试用例Spec执行之前做一些初始化操作,Teardown在每个Sepc执行完之后做一些清理操作,这两个函数名称来自于JUnit),是由一组全局beforeEach,afterEach, beforeAll,afterAll函数来实现的。
    beforeEach():在describe函数中每个Spec执行之前执行。
    afterEach(): 在describe函数中每个Spec数执行之后执行。
    beforeAll():在describe函数中所有的Specs执行之前执行,但只执行一次,在Sepc之间并不会被执行。
    afterAll(): 在describe函数中所有的Specs执行之后执行,但只执行一次,在Sepc之间并不会被执行。
beforeAll 和 afterAll适用于执行比较耗时或者耗资源的一些共同的初始化和清理工作。而且在使用时还要注意,它们不会在每个Spec之间执行,所以不适用于每次执行前都需要干净环境的Spec。
(7)this关键字
除了在describe函数开始定义变量,用于各it函数共享数据外,还可以通过this关键字来共享数据。
在每一个Spec的生命周期(beforeEach->it->afterEach)的开始,都将有一个空的this对象(在开始下一个Spec周期时,this会被重置为空对象)。
3.高级特性
1.Spy
Spy能监测任何function的调用和方法参数的调用痕迹。需使用2个特殊的Matcher:
    toHaveBeenCalled:可以检查function是否被调用过,
    toHaveBeenCalledWith: 可以检查传入参数是否被作为参数调用过。
2.spyOn
使用spyOn(obj,'function')来为obj的function方法声明一个Spy。不过要注意的一点是,对Spy函数的调用并不会影响真实的值。
describe("A spy", function() {
  var foo, bar = null;
 
  beforeEach(function() {
    foo = {
      setBar: function(value) {
        bar = value;
      }
    };
 
    spyOn(foo, 'setBar');
 
    foo.setBar(123);
    foo.setBar(456, 'another param');
  });
 
  it("tracks that the spy was called", function() {
    expect(foo.setBar).toHaveBeenCalled();
  });
 
  it("tracks all the arguments of its calls", function() {
    expect(foo.setBar).toHaveBeenCalledWith(123);
    expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
  });
 
  it("stops all execution on a function", function() {
    // Spy的调用并不会影响真实的值,所以bar仍然是null。
    expect(bar).toBeNull();
  });
});
3.and.callThrough
如果在spyOn之后链式调用and.callThrough,那么Spy除了跟踪所有的函数调用外,还会直接调用函数的真实实现,因此Spy返回的值就是函数调用后实际的值了。
  ...
  spyOn(foo, 'getBar').and.callThrough();
  foo.setBar(123);
  fetchedBar = foo.getBar();
 
  it("tracks that the spy was called", function() {
    expect(foo.getBar).toHaveBeenCalled();
  });
 
  it("should not effect other functions", function() {
    expect(bar).toEqual(123);
  });
 
  it("when called returns the requested value", function() {
    expect(fetchedBar).toEqual(123);
  });
});
4.全局匹配谓词
(1)jasmine.any
jasmine.any的参数为一个构造函数,用于检测该参数是否与实际值所对应的构造函数相匹配。
describe("jasmine.any", function() {
  it("matches any value", function() {
    expect({}).toEqual(jasmine.any(Object));
    expect(12).toEqual(jasmine.any(Number));
  });
 
  describe("when used with a spy", function() {
    it("is useful for comparing arguments", function() {
      var foo = jasmine.createSpy('foo');
      foo(12, function() {
        return true;
      });
 
      expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
    });
  });
});
(2)jasmine.anything
jasmine.anything用于检测实际值是否为nullundefined,如果不为nullundefined,则返回true
it("matches anything", function() {
    expect(1).toEqual(jasmine.anything());});
(3)jasmine.objectContaining
用于检测实际Object值中是否存在特定key/value对。
  var foo;
 
  beforeEach(function() {
    foo = {
      a: 1,
      b: 2,
      bar: "baz"
    };
  });
 
  it("matches objects with the expect key/value pairs", function() {
    expect(foo).toEqual(jasmine.objectContaining({
      bar: "baz"
    }));
    expect(foo).not.toEqual(jasmine.objectContaining({
      c: 37
    }));
  });
5.Jasmine Clock
Jasmine Clock用于setTimeout和setInterval的回调控制,它使timer的回调函数同步化,不再依赖于具体的时间,而是将时间离散化,使测试人员能精确控制具体的时间点。
调用jasmine.clock().install()可以在特定的需要操纵时间的Spec或者Suite中安装Jasmine Clock,注意操作完后要调用jasmine.clock().uninstall()进行卸载。
var timerCallback;
  beforeEach(function() {
    timerCallback = jasmine.createSpy("timerCallback");
    jasmine.clock().install();
  });
  afterEach(function() {
    jasmine.clock().uninstall();
  });
6.模拟超时(Mocking Timeout)
可以调用jasmine.clock().tick(nTime)来模拟计时,一旦tick中设置的时间nTime,其累计设置的值达到setTimeout或setInterval中指定的延时时间,则触发回调函数。
  it("causes an interval to be called synchronously", function() {
    setInterval(function() {
      timerCallback();
    }, 100);
 
    expect(timerCallback).not.toHaveBeenCalled();
 
    jasmine.clock().tick(101);
    expect(timerCallback.calls.count()).toEqual(1);
 
    jasmine.clock().tick(50);
    expect(timerCallback.calls.count()).toEqual(1);
    //tick设置的时间,累计到此201ms,因此会触发setInterval中的毁掉函数被调用2次。
    jasmine.clock().tick(50);
    expect(timerCallback.calls.count()).toEqual(2);
  });
三 模拟
1.什么是mock
在开始写测试之前,我们需要理解测试的一个核心特性:模拟。模拟允许我们在受控环境下定义模拟对象来模仿真实对象的行为。AngularJS提供了自己的模拟库:angular-mocks,位于angular-mock.js文件中,因此如果要在单元测试中建立模拟对象,就必须确保在Karma配置中,即test/karma.conf.js文件的file数组中包含了angular-mock.js。
2.ng-mock中的一些常用的方法
(1)angular.mock.module
此方法非常方便调用,因为angular.mock.module函数被发布在全局作用域的window接口上了。
module是用来配置inject方法注入的模块信息,参数可以是字符串,函数,对象,它一般用在beforeEach方法里,因为这个可以确保在执行测试任务的时候,inject方法可以获取到模块配置。
describe('myApp',function(){
//模拟'myApp'angular模块
beforeEach(angular.mock.module('myApp'));
it('....')
});
建立了模拟的angular模块之后,可以把连接到这个模块上的任意服务注入到测试代码中。在我们的测试代码中,注入依赖关系很重要,因为我们隔离了想要测试的功能。
(2)angular.mock.inject
inject函数也是在window对象上的,为的是全局访问,因此可以直接调用inject。
inject是用来注入上面配置好的ng模块,方便在it的测试函数里调用。
describe('myApp',function(){
var scope;
beforeEach(angular.mock.module('myApp'));
beforeEach(angular.mock.inject(function($rootscope){
     scope=$rootscope.$new();
});
it('...');
});
通常我们会用将引入进测试时使用的名字来保存它。比如说,如果我们在测试一个服务,可以注入这个服务,然后把它的引用用一种稍微不同的命名方案存储起来。在注入的服务名称两端使用下划线,当它被注入时,注入器会忽略它的名称。
describe('myApp',function(){
var scope;
beforeEach(angular.mock.module('myApp'));
beforeEach(angular.mock.inject(function(_myService_){
     myService=_myService_;
});
it('...');
});
3.模拟$httpBackend
angualr内置了$httpBackend模拟库,这样我们可以在应用中模拟任何外部的XHR请求,避免在测试中创建昂贵的$http请求。
如:
var app = angular.module('Application', []);
 
app.controller('MainCtrl', function($scope, $http) {
    $http.get('Users/users.json').success(function(data){
        $scope.users = data;
    });
    $scope.text = 'Hello World!';
});
测试:
describe('MainCtrl', function() {
    //我们会在测试中使用这个scope
    var scope, $httpBackend;
 
    //模拟我们的Application模块并注入我们自己的依赖
    beforeEach(angular.mock.module('Application'));
 
    //模拟Controller,并且包含 $rootScope 和 $controller
    beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_) {
        //设置$httpBackend冲刷$http请求
        $httpBackend = _$httpBackend_;
        $httpBackend.when('GET', 'Users/users.json').respond([{
            id: 1,
            name: 'Bob'
        }, {
            id: 2,
            name: 'Jane'
        }]);
        //创建一个空的 scope
        scope = $rootScope.$new();
 
        //声明 Controller并且注入已创建的空的 scope
        $controller('MainCtrl', {
            $scope: scope
        });
    }));
 
    // 测试从这里开始
    it('should have variable text = "Hello World!"', function() {
        expect(scope.text).toBe('Hello World!');
    });
    it('should fetch list of users', function() {
        $httpBackend.flush();
        expect(scope.users.length).toBe(2);
        expect(scope.users[0].name).toBe('Bob');
        //输出结果以方便查看
        for(var i=0;i<scope.users.length;i++){
            console.log(scope.users[i].name);
        }
    });
});
可以使用$httpBackend.when和$httpBackend.expect提前设置请求的伪数据,最后在请求后执行$httpBackend.flush就会立即执行完成http请求。
4.$httpBackend常用方法
(1)when :新建一个后端定义(backend definition)。
when(method, url, [data], [headers]);
(2)expect :新建一个请求期望(request expectation)。
expect(method, url, [data], [headers]);
method表示http方法注意都需要是大写(GET, PUT…);
url请求的url可以为正则或者字符串;
data请求时带的参数,
headers请求时设置的header。
如果这些参数都提供了,那只有当这些参数都匹配的时候才会正确的匹配请求。when和expect都会返回一个带respond方法的对象。respond方法有3个参数status,data,headers通过设置这3个参数就可以伪造返回的响应数据了。
$httpBackend.when与$httpBackend.expect的区别在于:$httpBackend.expect的伪后台只能被调用一次(调用一次后会被清除),第二次调用就会报错,而且$httpBackend.resetExpectations可以移除所有的expect而对when没有影响。
快捷方法:when和expect都有对应的快捷方法whenGET, whenPOST,whenHEAD, whenJSONP, whenDELETE, whenPUT; expect也一样。
使用快捷方法进行测试:
$httpBackend.whenGET('/someUrl').respond({name:'wolf'},{'X-Record-Count':100});   //声明Mock服务,模拟后端服务器行为
//调用网络接口
$http.get('/someUrl').success(function(data){
     expect(data.name).toBe('wolf');
});
//刷新一次,模拟后端返回请求,在调用这个命令之前,success中的回调函数不会被执行
$httpBackend.flush();

angularJS测试一 Karma Jasmine Mock的更多相关文章

  1. AngularJS测试框架 karma备忘

    AngularJS测试框架karma安装 安装karma $ --save-dev 安装karma组件 $ npm install karma-jasmine karma-chrome-launche ...

  2. 利用Angularjs测试引擎Karma进行自动化单元测试

    Karma是Google用于angularjs框架单元测试的js引擎(javascript test runner ), angular1 和angular2项目源码的单元测试都是基于karma和ja ...

  3. angularjs, nodejs, express, gulp, karma, jasmine 前端方案整合

    今年转向做前端开发,主要是做angularjs开发,期间接触了nodejs平台,从此一发不可收拾. npm丰富的插件库,express 开发框架, grunt, gulp构建工具,karma测试管理工 ...

  4. angular测试-Karma + Jasmine配置

    首先讲一下大致的流程: 需要node环境,首先先要安装node,node不会?请自行搜索.版本>0.8 安装node完成之后先要测试下npm是否测试通过,如下图所示 首先看下目录结构 目录为:F ...

  5. 在WebStorm中集成Karma+jasmine进行前端单元测试

    在WebStorm中集成Karma+jasmine进行前端单元测试 前言 好久没有写博了,主要还是太懒=.=,有点时间都去带娃.看书了,今天给大家分享一个原创的小东西,如果大家对TDD或者BDD有兴趣 ...

  6. angularjs自动化测试系列之jasmine

    angularjs自动化测试系列之jasmine jasmine参考 html <!DOCTYPE html> <html lang="en"> <h ...

  7. Karma +Jasmine+ require JS进行单元测试并生成测试报告、代码覆盖率报告

    1. 关于Karma Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner). 该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuou ...

  8. karma+jasmine自动化测试

    1.安装nodejs,进入项目目录 2.安装karma和相关插件 npm install karma --save-dev npm install karma-jasmine karma-chrome ...

  9. 使用karma+jasmine做单元测试

    目的 使用karma和jasmine来配置自动化的js单元测试. Karma和Jasmine Karma是由Angular团队所开发的一种自动化测试工具.链接:http://karma-runner. ...

随机推荐

  1. Alljoyn 概述(1)

    Alljoyn Overview Feb. 2012- AllJoyn 是什么? • 2011年2月9日发布,由 QuiC(高通创新中心)开发维护的开源软 件项目,采用 Apache license ...

  2. IE6 png兼容问题

    1.IE6 png  <!--[if IE 6]>  <script src="../js/png.js" type="text/javascript& ...

  3. IOS DLNA PlatinumKit库的使用

    前段时间进行了IOS DLNA的开发,使用的是PlatinumKit库.网上查了很多资料都未果,经过自己的摸索,遂将如何使用PlatinumKit进行DLNA的开发分享给大家. 1.PlatinumK ...

  4. [LeetCode OJ] Gas Station

    问题描述: There are N gas stations along a circular route, where the amount of gas at station i is gas[i ...

  5. 【转】linux 必须掌握的60个命令

    Linux必学的60个命令Linux提供了大量的命令,利用它可以有效地完成大量的工作,如磁盘操作.文件存取.目录操作.进程管理.文件权限设定等.所以,在Linux系统上工作离不开使用系统提供的命令.要 ...

  6. jQuery height()、innerHeight()、outerHeight()函数的区别

    参考: http://www.365mini.com/tech 函数 高度范围 jQuery版本 支持写操作 height() height 1.0+ 1.0+ innerHeight() heigh ...

  7. 升级Python至2.7.8,并安装django

    1:下载Python-2.7.8.tgz2:步骤:tar -zxvf Python-2.7.8.tgzcd Python-2.7.8./configure -h --查看configure选项./co ...

  8. 教你在Java的普通类中轻松获取Session以及request中保存的值

    曾经有多少人因为不知如何在业务类中获取自己在Action或页面上保存在Session中值,当然也包括我,但是本人已经学到一种办法可以解决这个问题,来分享下,希望对你有多多少少的帮助! 如何在Java的 ...

  9. 45 Useful JavaScript Tips, Tricks and Best Practices(有用的JavaScript技巧,技巧和最佳实践)

    As you know, JavaScript is the number one programming language in the world, the language of the web ...

  10. mouseenter 和 mouseleave

    做一个下拉菜单的时候,HTML结构如下: <ul> <li class="red"> <a href="">第一级</ ...