前端測試-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 初探

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Jasmine是什么 Jasmine是一個Javascript BDD測試框架。只要是Javascript能運行的...
    做測試的DanteYu閱讀 1,401評論 0 3
  • Unit Test 單元測試概念(Unit Testing)又稱為模塊測試, 是針對程序模塊(軟件設計的最小單位)...
    點柈閱讀 1,391評論 0 4
  • 初識Jasmine Jin Sun, January 17, 2016 我們要聊些什么: 一個不錯的引子 簡單粗暴...
    孫進不后退閱讀 4,954評論 3 13
  • 基本概念 suites suites表示一個測試集,以函數describe封裝 describe describe...
    只是無情緒閱讀 1,072評論 0 0
  • 昨晚,我夢到了奶奶。 奇怪的是,自從奶奶去世之后,我便再也沒有夢過她。昨夜,我卻在我那朦朧的夢中遇見了她。她的臉龐...
    Rachelyang0420閱讀 331評論 1 0