Jasmine Unit Test

作者: 点柈 | 来源:发表于2016-09-05 21:56 被阅读208次

    Unit Test

    • 单元测试概念(Unit Testing)

      又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

    • 单元测试必要性

      随着项目规模的增加,函数、方法、变量都在递增,维护的难度不断加大,以及测试提出的各种bug导致修改代码的时候会将原本整洁的代码变得混乱。
      经常出现同一个接口以不同的名称出现在不同的控制器中,这个时候往往会去重构代码,但是重构代码的时候没人会保证自己将万无一失,重构的代码还是正确的,方法一样跑通等等。这个时候就需要单元测试了,单元测试是一个衡量标准,告诉开发人员这么做是否将改变结果。保证重构后的代码的兼容性,减少人力测试的过程,降低维护成本。

    <h2>Jasmine</h2>

    Jasmine是一个behavior-driven development ( 行为驱动开发 ) 测试框架, 不依赖于任何其他JavaScript框架, 不依赖DOM, 并且有很简洁的语法让你能够很轻松的编写单元测试。它既可以在html文件中运行,也可以和jsTestDriver整合,在jsTestDriver中运行。
    
    • BDD 行为驱动开发,是一种新的敏捷开发方法。相对于TDD(测试驱动开发),它更趋向于需求,需要共同利益者的参与,强调用户故事和行为;是面向开发者、QA、非技术人员或商业参与者共同参与和理解的开发活动,而不是TDD简单地只关注开发者的方法论;

    • TDD测试驱动开发,是一种不同于传统软件开发流程:开发结束再测试介入的新型开发方法。要求把项目按功能点划分,在编写每个功能点前先编写测试代码,然后再编写使测试通过的功能代码,通过测试来推动整个开发工作。

    <h2>搭建环境</h2>

    <h4>1.下载源文件</h4>

    jasmine源文件下载地址

    图1.png
    下载jasmine-standlone-2.5.0.zip即可。这是一个范例,但是可以直接使用。运行起来如下图显示:
    
    运行图

    <h4>2.使用</h4>
    将下载下来的文件夹中lib文件夹下的jasmine-2.5.0文件夹直接拖入你所需要用的项目。在index.html 中引入下面几句

      <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.5.0/jasmine_favicon.png">
      <link rel="stylesheet" href="lib/jasmine-2.5.0/jasmine.css">
    
      <script src="lib/jasmine-2.5.0/jasmine.js"></script>
      <script src="lib/jasmine-2.5.0/jasmine-html.js"></script>
      <script src="lib/jasmine-2.5.0/boot.js"></script>
    
    
    之后便可以直接创建对应的测试用例js文件了。
    

    <h2>jasmine基础语法</h2>
    一个简单的例子

    describe("A suite", function() {  
        it("contains spec with an expectation", function() {  
            expect(true).toBe(true);  
        });  
    }); 
    
    

    <h3>1.两个核心方法</h3>

    • <h4>describe方法</h4>
      describe是jasmine用于描述测试集(Test Suite)的全局函数,作为测试集的开始,一般有两个参数,字符串和方法。字符串作为特定用例组的名字和标题。方法是包含实现用例组的代码。一个测试集合可以包含多个spec(测试点)。
    • <h4>it方法</h4>
      jasmine中用方法it来开始specs。it方法和describe方法类似, 同样有两个参数,一个String,一个function;String用来描述测试点(spec),function是具体的测试代码。

    示例代码

    describe("This is an exmaple suite", function() {
      it("contains spec with an expectation", function() {
        expect(true).toBe(true);
        expect(false).toBe(false);
        expect(false).not.toBe(true);
      });
    });
    

    <h3>2.四个核心概念</h3>
    <h4>Jasmine四个核心概念:</h4>

    • 分组(Suites)
    • 用例(Specs)
    • 期望(Expectations)
    • 匹配(Matchers)
    • 分组(Suites)

      Suites可以理解为一组测试用例,以函数describe(string,function)封装,describe函数接受两个参数,一个字符串和一个函数。字符串是这个Suites的名字或标题(通常描述下测试内容),函数是实现Suites的代码块。一个Suite可以包含多个Specs,一个Specs可以包括多个expect

    • 用例(Specs)

      Specs可以理解为一个测试用例,使用全局的Jasmin函数it创建。和describe一样接受两个参数,一个字符串和一个函数,函数就是要执行的测试代码,字符串就是测试用例的名字。一个Spec可以包含多个expectations来测试代码。

    • 期望(Expectations)

      Expectations由expect 函数创建。接受一个参数。和Matcher一起联用,设置测试的预期值。返回ture或false。
      在分组(describe)中可以写多个测试用例(it),也可以再进行分组(describe),在测试用例(it)中定义期望表达式(expect)和匹配判断(toBe)。看一个简单的Demo:

    describe("A suite", function() {//suites
        var a;
        it("A spec", function() {//spec
          a = true;
          expect(a).toBe(true);//expectations
        });
        
        describe("a suite", function() {//inner suites
               it("a spec", function() {//spec
               expect(a).toBe(true);//expectations
            });
      });
    });
    
    
    • 匹配(Matchers)

      Matcher实现断言的比较操作,一个“期望值”与“实际值”的对比,如果结果为true,则通过测试,反之,则失败。每一个matcher都能通过not执行否定判断。
    expect(a).toBe(true);//期望变量a为true
    expect(a).toEqual(true);//期望变量a等于true
    expect(a).toMatch(/reg/);//期望变量a匹配reg正则表达式,也可以是字符串
    expect(a.foo).toBeDefined();//期望a.foo已定义
    expect(a.foo).toBeUndefined();//期望a.foo未定义
    expect(a).toBeNull();//期望变量a为null
    expect(a.isMale).toBeTruthy();//期望a.isMale为真
    expect(a.isMale).toBeFalsy();//期望a.isMale为假
    expect(true).toEqual(true);//期望true等于true
    expect(a).toBeLessThan(b);//期望a小于b
    expect(a).toBeGreaterThan(b);//期望a大于b
    expect(a).toThrowError(/reg/);//期望a方法抛出异常,异常信息可以是字符串、正则表达式、错误类型以及错误类型和错误信息
    expect(a).toThrow();//期望a方法抛出异常
    expect(a).toContain(b);//期望a(数组或者对象)包含b
    
    自定义Matcher(被称为Matcher Factories)实质上是一个函数(该函数的参数可以为空),该函数返回一个闭包,该闭包的本质是一个compare函数,compare函数接受2个参数:actual value 和 expected value。  
    compare函数必须返回一个带pass属性的结果Object,pass属性是一个Boolean值,表示该Matcher的结果(为true表示该Matcher实际值与预期值匹配,为false表示不匹配),也就是说,实际值与预期值具体的比较操作的结果,存放于pass属性中。  
    
    其他matchers:

    jasmine.any(Class)--传入构造函数或者类返回数据类型作为期望值,返回true表示实际值和期望值数据类型相同:

    it("matches any value", function() {
        expect({}).toEqual(jasmine.any(Object));
        expect(12).toEqual(jasmine.any(Number));
    });
    

    jasmine.anything()--如果实际值不是null或者undefined则返回true:

    it("matches anything", function() {
        expect(1).toEqual(jasmine.anything());
    });
    

    jasmine.objectContaining({key:value})--实际数组只要匹配到有包含的数值就算匹配通过:

    foo = {
          a: 1,
          b: 2,
          bar: "baz"
    };
    expect(foo).toEqual(jasmine.objectContaining({bar: "baz"}));
    

    jasmine.arrayContaining([val1,val2,...])--stringContaining可以匹配字符串的一部分也可以匹配对象内的字符串:

    expect({foo: 'bar'}).toEqual({foo: jasmine.stringMatching(/^bar$/)});
    expect('foobarbaz').toEqual({foo: jasmine.stringMatching('bar')});
    

    3.Setup和Teardown方法

    为了减少重复性的代码,jasmine提供了beforeEach、afterEach、beforeAll、afterAll方法。 
    
    • beforeEach() :在describe函数中每个Spec执行之前执行;
    • afterEach() :在describe函数中每个Spec执行之后执行;
    • beforeAll() :在describe函数中所有的Specs执行之前执行,且只执行一次
    • afterAll () : 在describe函数中所有的Specs执行之后执行,且只执行一次
    describe("A spec (with setup and tear-down)", function () {
        var foo;    
        //beforeAll 在所有的it方法执行之前执行一次
        beforeAll(function () { 
               foo = 1;       
              console.log("beforeAll run");   
       });    
    
       //afterAll 在所有的it方法执行之后执行一次
        afterAll(function () {       
            foo = 0;        
            console.log("afterAll run");   
        });   
    
      //beforeEach 在每个it方法执行之前都执行一次
       beforeEach(function () {        
            console.log("beforeEach run");   
       });    
    
     //afterEach 在每个it方法执行之后都执行一次
      afterEach(function () {        
          console.log("afterEach run");    
      });    
    
      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);    
      });
    });
    

    结果如图所示:

    结果

    4.describe函数的嵌套

    每个嵌套的**describe**函数,都可以有自己的**beforeEach**,**afterEach**函数。  
    在执行每个内层**Spec**时,都会按嵌套的由外及内的顺序执行每个**beforeEach**函数,所以内层**Sepc**可以访问到外层**Sepc**中的**beforeEach**中的数据。类似的,当内层**Spec**执行完成后,会按由内及外的顺序执行每个**afterEach**函数。
    
    describe("A spec", 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);
      });
    
      describe("nested inside a second describe", function() {
        var bar;
    
        beforeEach(function() {
          bar = 1;
        });
    
        it("can reference both scopes as needed", function() {
          expect(foo).toEqual(bar);
        });
      });
    });
    

    5.禁用Suites,挂起Specs

    **Suites**可以被**Disabled**。在**describe**函数名之前添加**x**即可将**Suite**禁用。  
    被**Disabled**的**Suites**在执行中会被跳过,该**Suite**的结果也不会显示在结果集中。  
    
    xdescribe("A spec", function() {
      var foo;
    
      beforeEach(function() {
        foo = 0;
        foo += 1;
      });
    
      it("is just a function, so it can contain any code", function() {
        expect(foo).toEqual(1);
      });
    });
    
    有3种方法可以将一个Spec标记为Pending。被Pending的Spec不会被执行,但是Spec的名字会在结果集中显示,只是标记为Pending。
    
    • 如果在Spec函数it的函数名之前添加x(xit),那么该Spec就会被标记为Pending。
    • 一个没有定义函数体的Sepc也会在结果集中被标记为Pending。
    • 如果在Spec的函数体中调用pending()函数,那么该Spec也会被标记为Pending。pending()函数接受一个字符串参数,该参数会在结果集中显示在PENDING WITH MESSAGE:之后,作为为何被Pending的原因。
    describe("Pending specs", function() {
    
      xit("can be declared 'xit'", function() {
        expect(true).toBe(false);
      });
    
      it("can be declared with 'it' but without a function");
      
      it("can be declared by calling 'pending' in the spec body", function() {
        expect(true).toBe(false);
        pending('this is why it is pending');
      });
    });
    

    6.Spy追踪

    Jasmine具有函数的追踪和反追踪的双重功能,这东西就是Spy。Spy能够存储任何函数调用记录和传入的参数,Spy只存在于describe和it中,在spec执行完之后销毁。

    describe("A spy", function() {
      var foo, bar = null;
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          }
        };
        spyOn(foo, 'setBar');//给foo对象的setBar函数绑定追踪
        foo.setBar(123);
        foo.setBar(456, 'another param');
      });
      it("tracks that the spy was called", function() {
        expect(foo.setBar).toHaveBeenCalled();//toHaveBeenCalled用来匹配测试函数是否被调用过
      });
      it("tracks all the arguments of its calls", function() {
        expect(foo.setBar).toHaveBeenCalledWith(123);//toHaveBeenCalledWith用来匹配测试函数被调用时的参数列表
        expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');//期望foo.setBar已经被调用过,且传入参数为[456, 'another param']
      });
      it("stops all execution on a function", function() {
        expect(bar).toBeNull();//用例没有执行foo.setBar,bar为null
      });
    });
    

    and.callThrough--spy链式调用and.callThrough后,在获取spy的同时,调用实际的函数。

    describe("A spy, when configured to call through", function() {
      var foo, bar, fetchedBar;
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          },
          getBar: function() {
            return bar;
          }
        };
        spyOn(foo, 'getBar').and.callThrough();//调用and.callThrough方法
        foo.setBar(123);
        fetchedBar = foo.getBar();//因为and.callThrough,这里执行的是foo.getBar方法,而不是spy的方法
      });
      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);
      });
    });
    
    

    and.returnValue--spy链式调用and.returnValue 后,任何时候调用该方法都只会返回指定的值,

    describe("A spy, when configured to fake a return value", function() {
      var foo, bar, fetchedBar;
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          },
          getBar: function() {
            return bar;
          }
        };
        spyOn(foo, "getBar").and.returnValue(745);//指定返回值为745
        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(745);//默认返回指定的returnValue值
      });
    });
    

    and.callFake--spy链式添加and.callFake相当于用新的方法替换spy的方法

    describe("A spy, when configured with an alternate implementation", function() {
      var foo, bar, fetchedBar;
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          },
          getBar: function() {
            return bar;
          }
        };
        spyOn(foo, "getBar").and.callFake(function() {//指定callFake方法
          return 1001;
        });
        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(1001);//执行callFake方法,返回1001
      });
    });
    

    and.throwError--spy链式调用and.callError后,任何时候调用该方法都会抛出异常错误信息:

    describe("A spy, when configured to throw an error", function() {
      var foo, bar;
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          }
        };
        spyOn(foo, "setBar").and.throwError("error");//指定throwError
      });
      it("throws the value", function() {
        expect(function() {
          foo.setBar(123)
        }).toThrowError("error");//抛出错误异常
      });
    });
    

    and.stub--spy恢复到原始状态,不执行任何操作。直接看下代码:

    describe("A spy", function() {
      var foo, bar = null;
      beforeEach(function() {
        foo = {
          setBar: function(value) {
            bar = value;
          }
        };
        spyOn(foo, 'setBar').and.callThrough();
      });
      it("can call through and then stub in the same spec", function() {
        foo.setBar(123);
        expect(bar).toEqual(123);
        foo.setBar.and.stub();//把foo.setBar设置为原始状态,and.callThrough无效
        bar = null;
        foo.setBar(123);//执行赋值无效
        expect(bar).toBe(null);
      });
    });
    

    Spy的其他方法

    .calls.any():记录spy是否被访问过,如果没有,则返回false,否则,返回true;
    .calls.count():记录spy被访问过的次数;
    .calls.argsFor(index):返回指定索引的参数;
    .calls.allArgs():返回所有函数调用的参数记录数组;
    .calls.all ():返回所有函数调用的上下文、参数和返回值;
    .calls.mostRecent():返回最近一次函数调用的上下文、参数和返回值;
    .calls.first():返回第一次函数调用的上下文、参数和返回值;
    .calls.reset():清除spy的所有调用记录;
    
    还有几种方法,诸如异步,ajax等等,我还只是入门,不甚了解,demo的话这之后我弄好会放上来。

    参考:

    官方文档
    jasmine测试框架简介
    JavaScript单元测试框架-Jasmine
    JavaScript 单元测试框架:Jasmine 初探
    web前端开发七武器—Jasmine入门教程(上)
    前端测试-jasmine
    开启JavaScript测试之路--Jasmine

    相关文章

      网友评论

        本文标题:Jasmine Unit Test

        本文链接:https://www.haomeiwen.com/subject/nrirettx.html