前端測試-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閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

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