前端测试-jasmine

写在前面

本文会介绍一些jasmine的基本概念和语法,并给出简单易懂的示例。适合初学jasmine者,如果你已经接触并使用过jasmine,可能并不太适合你

Jasmine 前端单元测试框架

Jasmine是面向行为驱动开发(BDD)的Javascript单元测试框架。它不依赖于其他任何javascript框架,语法清晰简单,很容易上手写出测试代码

  • BDD 行为驱动开发,是一种新的敏捷开发方法。相对于TDD(测试驱动开发),它更趋向于需求,需要共同利益者的参与,强调用户故事和行为;是面向开发者、QA、非技术人员或商业参与者共同参与和理解的开发活动,而不是TDD简单地只关注开发者的方法论;
  • TDD测试驱动开发,是一种不同于传统软件开发流程:开发结束再测试介入的新型开发方法。要求把项目按功能点划分,在编写每个功能点前先编写测试代码,然后再编写使测试通过的功能代码,通过测试来推动整个开发工作。

如果你想深入了解BDD和TDD:
可阅读下关于前端开发谈谈单元测试这篇文章,另外整理了基本书籍,推荐给大家:

  • 测试驱动开发byExample
  • 测试驱动开发的艺术

学习jasmine

jasmine学习环境搭建

在开始学习jasmine之前,搭建一个测试jasmine语法的学习环境是很有必要的,但实际开发中不推荐使用这样的环境

获取安装包

可以在开源社区github上下载 jasmine-standalone-2.4.1.zip;

配置安装

下载后解压.zip压缩包,得到如下的目录结构:

前端测试-jasmine_第1张图片
jasmine安装包解压后目录结构
  • lib目录下包含的jasmine的源代码,把他们(jasmine.css,jasmine.js,jasmine-html.js)引入页面中,就构造了一个jasmine的运行器(Runner);
  • 开发基于jasmine的测试用例并再引入到jasmine的运行器(页面)中,就开始测试工作了。
  
  
  
  

sepc,src 和SpecRunner.html是jasmine的一个官方完整example,使用浏览器直接打开SpecRunner.html就可以看到example的测试结果

jasmine语法介绍

阅读上例SpecRunner,你应该有了关于jasmine测试用例如何去写的问题,带着问题来学习下jasmine的语法是再好不过的方式了

describe方法

describe是jasmine用于描述测试集(Test Suite)的全局函数,通常有两个参数,一个字符串String,一个方法function;字符串用来描述Test suite,function里的代码就是测试代码,表示一个测试集合;
一个测试集合可以包含多个spec(测试点)

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

jasmine使用it来定义spec(测试点),it方法很像describe,同样有两个参数,一个String,一个function。String用来描述测试点(spec),function就是具体的测试代码;
一个测试点(sepc)可以包含多个expectations(断言)

expectations

断言以expect语句来表示,返回ture或false;
expect有一个参数, 代表测试的实际值,它和表示匹配规则的Matcher链接在一起,Matcher带有期望值;
全部的断言返回true,这个测试点才通过,只要有一个断言返回false,测试点不通过

describe("A suite is just a function",function(){
     var a;
     var b;
     it("and so is a spec",function(){
        a=true;
        b=false;
        expect(a).toBe(true);
        expect(b).toBe(true);
    })
 })
Matchers

Matcher实现了断言的比较操作,将expectation传入的实际值和Matcher传入的期望值进行比较,得出断言的结果true or false;

否定断言:任何Matcher都能通过在expect调用Matcher前加上 not来现实否定断言;

describe("Included matchers:", function() {

        it("The 'toBe' matcher compares with ===", function() {
            var a = 12;
            var b = a;

            expect(a).toBe(b);
            expect(a).not.toBe(null);
        });  
        //上面的例子,比较a、b是否相等;验证a是否不是空。 

        it("should work for objects", function() {
            var foo = {
                a: 12,
                b: 34
            };
            var bar = {
                a: 12,
                b: 34
            };
            expect(foo).toEqual(bar);
        });
        //上面的例子比较了两个对象是否相等
    });

    it("The 'toMatch' matcher is for regular expressions", function() {
        var message = 'foo bar baz';

        expect(message).toMatch(/bar/);
        expect(message).toMatch('bar');
        expect(message).not.toMatch(/quux/);
    });
    //也可以使用正则表达式
    it("The 'toBeDefined' matcher compares against `undefined`", function() {
        var a = {
            foo: 'foo'
        };

        expect(a.foo).toBeDefined();
        expect(a.bar).not.toBeDefined();
    });
    //验证变量是否被定义  

    it("The 'toBeNull' matcher compares against null", function() {
        var a = null;
        var foo = 'foo';

        expect(null).toBeNull();
        expect(a).toBeNull();
        expect(foo).not.toBeNull();
    });
    //验证是否为空

    it("The 'toBeTruthy' matcher is for boolean casting testing", function() {
        var a, foo = 'foo';

        expect(foo).toBeTruthy();
        expect(a).not.toBeTruthy();
    });

    it("The 'toBeFalsy' matcher is for boolean casting testing", function() {
        var a, foo = 'foo';

        expect(a).toBeFalsy();
        expect(foo).not.toBeFalsy();
    });
    //变量是否能够转化成boolean变量? 不太确定

    it("The 'toContain' matcher is for finding an item in an Array", function() {
        var a = ['foo', 'bar', 'baz'];

        expect(a).toContain('bar');
        expect(a).not.toContain('quux');
    });
    //是否包含
    it("The 'toBeLessThan' matcher is for mathematical comparisons", function() {
        var pi = 3.1415926, e = 2.78;

        expect(e).toBeLessThan(pi);
        expect(pi).not.toBeLessThan(e);
    });

    it("The 'toBeGreaterThan' is for mathematical comparisons", function() {
        var pi = 3.1415926, e = 2.78;

        expect(pi).toBeGreaterThan(e);
        expect(e).not.toBeGreaterThan(pi);
    });
    //数学大小的比较

    it("The 'toBeCloseTo' matcher is for precision math comparison", function() {
    var pi = 3.1415926, e = 2.78;

    expect(pi).not.toBeCloseTo(e, 2);
    expect(pi).toBeCloseTo(e, 0);
    });
    //两个数值是否接近,这里接近的意思是将pi和e保留一定小数位数后,是否相等。(一定小数位数:默认为2,也可以手动指定)

    it("The 'toThrow' matcher is for testing if a function throws an exception", function() {
        var foo = function() {
        return 1 + 2;
        };
        var bar = function() {
            return a + 1;
        };

        expect(foo).not.toThrow();
        expect(bar).toThrow();
        });
    }); 
    //测试一个方法是否抛出异常  
Setup和Teardown方法

为了减少重复性的代码,jasmine提供了beforeEachafterEachbeforeAllafterAll方法。

  • 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);    
  });
});

上面代码在浏览器控制台的输出

前端测试-jasmine_第2张图片
beforeEach,beforeAll等方法执行顺序输出

很明显

  • beforeAll和afterAll在两个it执行前后,总的只执行了一遍;
  • beforeEach,afterEach在每次it执行前后,都执行了一遍,所以结果打印了两遍beforeEach runafterEach run
describe函数的嵌套

describe函数可以嵌套,嵌套中的每层都可以定义Specs(测试点)、beforeEach以及afterEach函数;

执行内层Spec时,会按嵌套由外到内的顺序执行每个beforeEach函数(所以内层Spec可以访问外层Spec中的beforeEach中的数据),另外当内层Spec执行完后,会按由内到外的顺序执行每个afterEach函数;

describe("A spec", function () {
    var foo;
    beforeAll(function () {
        console.log("outer beforeAll");
    });

    afterAll(function () {
        console.log("outer afterAll");
    });

    beforeEach(function () {
        foo = 0;
        foo += 1;
        console.log("outer beforeEach");
    });

    afterEach(function () {
        foo = 0;
        console.log("outer afterEach");
    });

    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;
        beforeAll(function () {
            console.log("inner beforeAll");
        });
        beforeEach(function () {
            bar = 1;
            console.log("inner beforeEach")
        });
        it("can reference both scopes as needed", function () {
            expect(foo).toEqual(bar);
        })
    })
});

同样,代码在浏览器控制台有输出

前端测试-jasmine_第3张图片
嵌套describe,beforeEach、befroreAll等执行顺序输出
自定义Matchers

自定义Matcher是一个函数,该函数返回一个闭包,该闭包实质是一个compare函数,compare接受2个参数:actual value(expect传入的实际值)和expected value(matcher函数传入的期望值);

compare函数必须返回一个带pass属性的Object,pass属性值是一个boolean值,表示Matcher的结果,换句话说,实际值和期望值比较的结果,存放在pass属性中;

测试失败的提示信息可以通过Object的message属性来定义,如果没定义message信息返回,则会jasmine会生成一个默认的错误信息提示;

var customMatchers = {
    toBeGoofy: function (util, customEqualityTesters) {
        return {
            compare: function (actual, expected) {
                if (expected === undefined) {
                    expected = '';
                }
                console.log(util);
                console.log(customEqualityTesters);
                var result = {}; 

                //比较结果true or false 通过pass 属性值返回
                result.pass = util.equals(actual.hyuk, "gawrsh" + expected, customEqualityTesters); 

                //messge定义
                if (result.pass) {
                    result.message = "Expected" + actual + "not to be quite so goofy";
                } else {
                    result.message = "Expected" + actual + "to be goofy,but it was not very goofy"; 
               }
                return result;
            }
        };
  }};

自定义Matcher构造函数接受两个参数,util:给Matcher使用的一组工具函数(equals,contains,buildFailureMessage) ;customEqualityTesters:调用util.equals时需要传入,仅此而已

自定义Matchers的使用

在定义完Matcher之后,就是使用它了。有两种使用Matcher的方法:

  • 将Matcher函数添加到特定describe函数的beforeEach中,方便该describe函数中的Spec都能调用得到它。其他非嵌套describe中的Spec是无法调用到它的;
describe("Custom matcher: 'toBeGoofy'", function() {
     beforeEach(function() {
        jasmine.addMatchers(customMatchers);
 });

 it("can take an 'expected' parameter", function() {
       expect({
            hyuk: 'gawrsh is fun'
          }).toBeGoofy(' is fun');
    });
});
  • 另外一种是将Matcher函数添加到全局beforeEach函数中,这样所有的Suites中的所有的Specs,都可以使用该Matcher。下面的例子引用自官方example
//定义
beforeEach(function () {
  jasmine.addMatchers({
    toBePlaying: function () {
      return {
        compare: function (actual, expected) {
          var player = actual; 
         return {
            pass: player.currentlyPlayingSong === expected && player.isPlaying
          };
        }
      };
    }
  });
});

//应用
describe("Player", function() {
   it("should be able to play a Song", function() {
     player.play(song);
     //demonstrates use of custom matcher
     expect(player).toBePlaying(song);
 });

 describe("when song has been paused", function() {
     it("should indicate that the song is currently paused",   function() {
     // demonstrates use of 'not' with a custom matcher
       expect(player).not.toBePlaying(song);
     });
)};
我的jasmine gitstart

https://github.com/unnKoel/jasmine-gitstart

参考

github jasmine

关于前端开发谈谈单元测试

JavaScript单元测试框架-Jasmine
Javascript测试框架Jasmine(四):自定义Matcher
jasmine测试框架简介
JavaScript 单元测试框架:Jasmine 初探

你可能感兴趣的:(前端测试-jasmine)