Jasmine的開發團隊來自PivotalLabs,他們一開始開發的JavaScript測試框架是JsUnit,來源於著名的JAVA測試框架JUnit。JsUnit是xUnit的JavaScript實現。可是JsUnit在2009年後就已經中止維護了,他們推出了一個新的BDD框架Jasmine。Jasmine不依賴於任何框架,因此適用於全部的Javascript代碼。javascript
所謂BDD(行爲驅動開發,Behaviour Driven Development),是一種新的敏捷開發方法。Dan North對BDD給出的定義爲:css
BDD是第二代的、由外及內的、基於拉(pull)的、多方利益相關者的(stakeholder)、多種可擴展的、高自動化的敏捷方法。它描述了一個交互循環,能夠具備帶有良好定義的輸出(即工做中交付的結果):已測試過的軟件。
BDD與TDD(Test Driven Development )的主要區別是,使得非程序人員也能參與到測試用例的編寫中來,大大下降了客戶、用戶、項目管理者與開發者之間來回翻譯的成本。因此BDD更加註重業務需求而不是技術[1]。html
在Jasmine的Github官方主頁:https://github.com/jasmine/jasmine
找到上方的releases,點擊會跳轉到https://github.com/jasmine/jasmine/releases。
下載已發佈的zip包,好比下載當前(2015-03-09)的最新版本爲:jasmine-standalone-2.2.0.zipjava
解壓以後,能夠看到有1個html文件和3個文件夾。git
jasmine-2.2.0
文件夾。能夠將不一樣版本的Jasmine放在lib下,以便使用時切換。
toBe
,toNotBe
等)不適用,就能夠額外添加本身的規則(在本文件中添加了自定義的規則toBePlaying
)。其中,spec文件夾,src文件夾和SpecRunner.html文件是Jasmine提供的一個完整示例,用瀏覽器打開 SpecRunner.html,便可看到執行的結果。github
SpecRunner.html
運行測試用例的例子:正則表達式
<html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.2.0</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.2.0/jasmine_favicon.png"> <link rel="stylesheet" href="lib/jasmine-2.2.0/jasmine.css"> <script src="lib/jasmine-2.2.0/jasmine.js"></script> <script src="lib/jasmine-2.2.0/jasmine-html.js"></script> <script src="lib/jasmine-2.2.0/boot.js"></script> <!-- include source files here... --> <script src="src/Player.js"></script> <script src="src/Song.js"></script> <!-- include spec files here... --> <script src="spec/SpecHelper.js"></script> <script src="spec/PlayerSpec.js"></script> </head> <body></body> </html>
框架中的一些核心概念,能夠參考官方文檔中的介紹[2]。下面進入搬磚模式:數組
Suite表示一個測試集,以函數describe(string, function)
封裝,它包含2個參數:
string
:測試組名稱,
function
:測試組函數。瀏覽器
一個Suite(describe
)包含多個Specs(it
),一個Specs(it
)包含多個斷言(expect
)。閉包
Jasmine的Setup和Teardown操做(Setup在每一個測試用例Spec執行以前作一些初始化操做,Teardown在每一個Sepc執行完以後作一些清理操做,這兩個函數名稱來自於JUnit),是由一組全局beforeEach
,afterEach
, beforeAll
,afterAll
函數來實現的。
describe
函數中每一個Spec執行以前執行。describe
函數中每一個Spec數執行以後執行。describe
函數中全部的Specs執行以前執行,但只執行一次,在Sepc之間並不會被執行。describe
函數中全部的Specs執行以後執行,但只執行一次,在Sepc之間並不會被執行。beforeAll
和 afterAll
適用於執行比較耗時或者耗資源的一些共同的初始化和清理工做。並且在使用時還要注意,它們不會在每一個Spec之間執行,因此不適用於每次執行前都須要乾淨環境的Spec。
除了在describe
函數開始定義變量,用於各it
函數共享數據外,還能夠經過this
關鍵字來共享數據。
在在每個Spec的生命週期(beforeEach->it->afterEach
)的開始,都將有一個空的this
對象(在開始下一個Spec週期時,this
會被重置爲空對象)。
describe
函數能夠嵌套,每層均可以定義Specs。這樣就可讓一個Suite由一組樹狀的方法組成。
每一個嵌套的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); }); }); });
Spec表示測試用例,以it(string, function)
函數封裝,它也包含2個參數:
string
:測試用例名稱,
function
:測試用例函數。
Expectation就是一個斷言,以expect
語句表示,返回true
或false
。expect
語句有1個參數,表明要測試的實際值(the actual)。
只有當一個Spec中的全部Expectations全爲ture
時,這個Spec才經過,不然失敗。
Expectation帶實際值,它和表示匹配規則的Matcher連接在一塊兒,Matcher帶有指望值。
Matcher實現了斷言的比較操做,將Expectation傳入的實際值和Matcher傳入的指望值比較。
任何Matcher都能經過在expect
調用Matcher前加上not
來實現一個否認的斷言(expect(a).not().toBe(false);
)。
經常使用的Matchers有:
===
比較。null
。true
。==
,注意與toBe()
的區別。expect({}).not().toBe({}); expect({}).toEqual({});
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); });
自定義Matcher(被稱爲Matcher Factories)實質上是一個函數(該函數的參數能夠爲空),該函數返回一個閉包,該閉包的本質是一個compare
函數,compare
函數接受2個參數:actual value 和 expected value。
compare
函數必須返回一個帶pass
屬性的結果Object,pass
屬性是一個Boolean值,表示該Matcher的結果(爲true
表示該Matcher實際值與預期值匹配,爲false
表示不匹配),也就是說,實際值與預期值具體的比較操做的結果,存放於pass
屬性中。
最後測試輸出的失敗信息應該在返回結果Object中的message
屬性中來定義。
var customMatchers = { toBeGoofy: function(util, customEqualityTesters) { return { compare: function(actual, expected) { if (expected === undefined) { expected = ''; } var result = {}; result.pass = util.equals(actual.hyuk, "gawrsh" + expected, customEqualityTesters); 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有2種使用方法:
describe
函數的beforeEach
中,以便該describe
函數中的全部Spec都能調用到它。但其餘describe
中並不能使用該Matcher。custom_matcher.js
的實現[3]。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'); }); });
beforeEach
函數中,這樣全部的Suites中的全部的Specs,均可以使用該Matcher。SpecHelper.js
文件中的toBePlaying
自定義的規則的實現。//定義 beforeEach(function () { jasmine.addMatchers({ toBePlaying: function () { // 自定義Matcher:toBePlaying return { //要返回的compare函數 compare: function (actual, expected) { var player = actual; //compare函數中要返回的結果Object,這裏是一個匿名Object,包含一個pass屬性。 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); }); )};
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。
it
的函數名以前添加x
(xit
),那麼該Spec就會被標記爲Pending。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'); }); });
Spy能監測任何function的調用和方法參數的調用痕跡。需使用2個特殊的Matcher:
toHaveBeenCalled
:能夠檢查function是否被調用過,toHaveBeenCalledWith
: 能夠檢查傳入參數是否被做爲參數調用過。使用spyOn(obj,'function')
來爲obj
的function
方法聲明一個Spy。不過要注意的一點是,對Spy函數的調用並不會影響真實的值。
describe("A spy", function() { var foo, bar = null; beforeEach(function() { foo = { setBar: function(value) { bar = value; } }; spyOn(foo, 'setBar'); foo.setBar(123); foo.setBar(456, 'another param'); }); it("tracks that the spy was called", function() { expect(foo.setBar).toHaveBeenCalled(); }); it("tracks all the arguments of its calls", function() { expect(foo.setBar).toHaveBeenCalledWith(123); expect(foo.setBar).toHaveBeenCalledWith(456, 'another param'); }); it("stops all execution on a function", function() { // Spy的調用並不會影響真實的值,因此bar仍然是null。 expect(bar).toBeNull(); }); });
若是在spyOn
以後鏈式調用and.callThrough
,那麼Spy除了跟蹤全部的函數調用外,還會直接調用函數額真實實現,所以Spy返回的值就是函數調用後實際的值了。
... spyOn(foo, 'getBar').and.callThrough(); 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(123); }); });
在調用and.callThrough後,若是你想阻止spi繼續對實際值產生影響,你能夠調用and.stub。也就是說,and.stub是將spi對實際實現的影響還原到最終的狀態——不影響實際值。
spyOn(foo, 'setBar').and.callThrough(); foo.setBar(123); // 實際的bar=123 expect(bar).toEqual(123); // 調用and.stub()後,以後調用foo.setBar將不會影響bar的值。 foo.setBar.and.stub(); foo.setBar(456); expect(bar).toBe(123); bar = null; foo.setBar(123); expect(bar).toBe(null);
jasmine.any
的參數爲一個構造函數,用於檢測該參數是否與實際值所對應的構造函數相匹配。
describe("jasmine.any", function() { it("matches any value", function() { expect({}).toEqual(jasmine.any(Object)); expect(12).toEqual(jasmine.any(Number)); }); describe("when used with a spy", function() { it("is useful for comparing arguments", function() { var foo = jasmine.createSpy('foo'); foo(12, function() { return true; }); expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function)); }); }); });
jasmine.anything
用於檢測實際值是否爲null
或undefined
,若是不爲null
或undefined
,則返回true
。
it("matches anything", function() { expect(1).toEqual(jasmine.anything()); });
用於檢測實際Object值中是否存在特定key/value對。
var foo; beforeEach(function() { foo = { a: 1, b: 2, bar: "baz" }; }); it("matches objects with the expect key/value pairs", function() { expect(foo).toEqual(jasmine.objectContaining({ bar: "baz" })); expect(foo).not.toEqual(jasmine.objectContaining({ c: 37 })); });
用於檢測實際Array值中是否存在特定值。
var foo; beforeEach(function() { foo = [1, 2, 3, 4]; }); it("matches arrays with some of the values", function() { expect(foo).toEqual(jasmine.arrayContaining([3, 1])); expect(foo).not.toEqual(jasmine.arrayContaining([6])); });
Jasmine Clock用於setTimeout
和setInterval
的回調控制,它使timer
的回調函數同步化,再也不依賴於具體的時間,而是將時間離散化,使測試人員能精確控制具體的時間點。
調用jasmine.clock().install()
能夠在特定的須要操縱時間的Spec或者Suite中安裝Jasmine Clock,注意操做完後要調用jasmine.clock().uninstall()
進行卸載。
var timerCallback; beforeEach(function() { timerCallback = jasmine.createSpy("timerCallback"); jasmine.clock().install(); }); afterEach(function() { jasmine.clock().uninstall(); });
能夠調用jasmine.clock().tick(nTime)
來模擬計時,一旦tick
中設置的時間nTime
,其累計設置的值達到setTimeout
或setInterval
中指定的延時時間,則觸發回調函數。
it("causes an interval to be called synchronously", function() { setInterval(function() { timerCallback(); }, 100); expect(timerCallback).not.toHaveBeenCalled(); jasmine.clock().tick(101); expect(timerCallback.calls.count()).toEqual(1); jasmine.clock().tick(50); expect(timerCallback.calls.count()).toEqual(1); //tick設置的時間,累計到此201ms,所以會觸發setInterval中的毀掉函數被調用2次。 jasmine.clock().tick(50); expect(timerCallback.calls.count()).toEqual(2); });
調用beforeEach
,it
或者afterEach
時,能夠添加一個可選參數(Function
類型,在官方文檔的例子中該參數爲done
)。當done
函數被調用,代表異步操做的回調函數調用成功;不然若是沒有調用done
,代表異步操做的回調函數調用失敗,則該Spec不會被調用,且會由於超時退出。
Jasmine等待異步操做完成的默認時間是5s,若是5s內異步操做沒有完成,則Spec會由於超時退出。超時時間也能夠經過全局的jasmine.DEFAULT_TIMEOUT_INTERVAL
修改[4]。
var value; // setTimeout表明一個異步操做。 beforeEach(function(done) { setTimeout(function() { value = 0; // 調用done表示回調成功,不然超時。 done(); }, 1); }); // 若是在beforeEach中的setTimeout的回調中沒有調用done,最終致使下面的it因超時而失敗。 it("should support async execution of test preparation and expectations", function(done) { value++; expect(value).toBeGreaterThan(0); done(); });
[1] Javascript的Unit Test。
[2] 官方文檔introduction.js
[3] 官方文檔custom_matcher.js
[4] Jasmine——JavaScript 單元測試框架