前端测试-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安装包解压后目录结构
  • lib目录下包含的jasmine的源代码,把他们(jasmine.css,jasmine.js,jasmine-html.js)引入页面中,就构造了一个jasmine的运行器(Runner);
  • 开发基于jasmine的测试用例并再引入到jasmine的运行器(页面)中,就开始测试工作了。
  <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.4.1/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine-2.4.1/jasmine.css">
  <script type="text/javascript" src="lib/jasmine-2.4.1/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-2.4.1/jasmine-html.js"></script>

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

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

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

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

嵌套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 初探

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Jasmine是什么 Jasmine是一个Javascript BDD测试框架。只要是Javascript能运行的...
    做测试的DanteYu阅读 1,354评论 0 3
  • Unit Test 单元测试概念(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)...
    点柈阅读 1,366评论 0 4
  • 初识Jasmine Jin Sun, January 17, 2016 我们要聊些什么: 一个不错的引子 简单粗暴...
    孙进不后退阅读 4,912评论 3 13
  • 基本概念 suites suites表示一个测试集,以函数describe封装 describe describe...
    只是无情绪阅读 1,015评论 0 0
  • 昨晚,我梦到了奶奶。 奇怪的是,自从奶奶去世之后,我便再也没有梦过她。昨夜,我却在我那朦胧的梦中遇见了她。她的脸庞...
    Rachelyang0420阅读 306评论 1 0