[Testing] Jasmine — JavaScript 的 BDD 測試架構
Jasmine是一個針對Javascript編程語言的行為驅動測試框架。Jasmine 在程式的撰寫上和RSpec非常類似:
describe("Book list", function(){
it("returns empty if none matches", function(){
//...
});
it("returns matched items", function(){
$("#query").val("Javascript");
$("#search").click();
expect($("#results").find("li")).toEqual(5);
});
});
使用 Jasmine 的 HTML 外掛程式(SpecRunner.html),在執行時期會產生 HTML 報表。
設置 Jasmine
下載Jasmine最新的獨立發佈版本(standalone release),解壓縮後修改SpecRunner.html文件。
或可以搭配其他環境使用:
group :development, :test do
gem 'jasmine'
end
安裝後執行:
$ jasmine init
$ rake jasmine:ci
describe, it and expect
首先我們在spec資料夾下建立 hello.spec.js
檔案:
// spec/hello.spec.js
describe("Hello world", function() {
it("says hello", function() {
expect(helloWorld()).toEqual("Hello world!");
// 如果只需檢查是否包含world可以使用
// expect(helloWorld()).toContain("world");
});
});
jasmine 使用 describe 語法定義一個測試集(suite), 這個測試集的名字是 hello world。it區塊叫做一個參數規格(specification),或者簡寫為一個spec,用來描述你的組件應該做些什麼。我們期望helloWorld( )的輸出等於(toEqual)字符串「Hello World!」,toEqual 則是一個匹配器(matcher)。
在SpecRunner.html中載入hello.spec.js
<script type="text/javascript" src="spec/hello.spec.js"></script>
此時運行SpecRunner.html會拋出錯誤,因爲我們還沒有實際代碼。接下來編寫實際代碼。在src資料夾下建立 hello.js
# src/hello.js
function helloWorld() {
return "Hello world!";
}
在SpecRunner.html中載入hello.js
,
<script type="text/javascript" src="src/hello.js"></script>
完成後測試將會通過。
主要原則
- 猶豫就測試
編寫spec需要花費你大量的時間, 但可用來做測試的時間可能沒有那麼多。 如果你對是否應該對某個功能進行測試猶豫,那就寫測試吧。
- 每個spec應該只測試一種情形或場景。
- 不需要去測試私有方法。
Matchers
- toEqual
expect([1, 2, 3]).toEqual([1, 2, 3]);
- toBe(檢查是否爲同一對象)
var array = [1, 2, 3];
expect(array).toEqual([1, 2, 3]); // true
expect(array).toBe([1, 2, 3]); // false, 不是同一對象
- toBeTruthy
- toBeFalsy
在Jasmine中被視作falsy的東西(在Javascript中也一樣): false / 0 / “” / undefined / null / NaN
- toContain
expect([1, 2, 3, 4]).toContain(3);
- toBeDefined
- toBeUndefined
- toBeNull
- toBeNaN
- toBeGreaterThan
- toBeLessThan
- toBeCloseTo(可給定一個小數精確程度作為第二個參數)
expect(12.34).toBeCloseTo(12.3, 1);
- toMatch(配合正則表達式)
expect("jasmine@example.com").toMatch("\w+@\w+\.\w+");
- toThrow(是否拋出錯誤)
用not來否定匹配器
expect(foo).not.toEqual(bar);
Custom Matcher
beforeEach(function() {
this.addMatchers({
toBeLarge: function() {
this.message = function() {
return "Expected " + this.actual + " to be large";
};
return this.actual > 100;
}
});
});
每一個匹配器從expect函數接收一個參數,這個參數就是this.actual。this.message是當匹配器運行失敗時返回的訊息。
beforeEach / afterEach
可將每個spec執行前重複使用的code整理到beforeEach()。反之, 想要在每個spec之後執行一些代碼,只需要將代碼放入afterEach()中。
跳過Specs
- 將 it 後面的 callback function 移除。
- 可以使用
pending();
語法來 pending 測試。 - 或是在 it 或 describe 前加上x。
- 使用return終止後面函數的執行
類型匹配
只關心類型是什麼,不在意回傳的值。
expect(rand()).toEqual(jasmine.any(Number));
- jasmine.any(Number)
- jasmine.any(String)
- jasmine.any(Object)
- jasmine.any(myObject)
SPY 功能
對程序進行監視。
var realBusiness = function(callback) {
$.ajax({
url: 'business.do',
success: function() {
callback(data);
}
})
};realBusiness(function(list) {
$('#map').html(list);
});
如果實際的程式中有對外部應用的相依,但在測試中我們不希望真的發送請求的時候,可以用spy來解決這個問題。
使用spyOn(object, method);
來監視,接着toHaveBeenCalled();
或是 toHaveBeenCalledWith();
確保程式有被執行。
describe('realBusiness', function() {
it('sends request to server', function() {
spyOn($, 'ajax');
realBusiness(undefined);
expect($.ajax).toHaveBeenCalled();
});
it('calls callback when success', function(){
var callback = function(){
spyOn($, 'ajax').andCallFake(function(){
// callback
e.success({});
});
};
realBusiness(callback);
expect($.ajax.mostRecentCall.args[0].url).toEqual('business.do');
});
});
當 spy 在 $.ajax 上,後面的程式中,無論何處呼叫 $.ajax,Jasmine 都不會真的呼叫 $.ajax,而是呼叫這個 spy,而且會記錄所有的呼叫記錄。
Spy Methods
spyOn(events, 'publish');
spyOn(events, 'publish').and.callThrough(); // 實際執行
spyOn(events, 'publish').and.returnValue(false); // 確保返回一個具體值
spyOn(events, 'publish').and.callFake(function(name, args) {
window.alert(name);
}); // 調用一個偽函數
spyOn(events, 'publish').and.throwError('oops'); // 確保拋出錯誤訊息
spyOn(events, 'publish').and.stub(); // set a spy back to a stub method
參考:
- Jasmine
- JavaScript Testing with Jasmine / O’Reilly
- 基於Jasmine 的 Javascript 測試
- 「還在寫PHP?大師才用輕量級Ruby、JavaScript開發Web」- 邱俊濤 / 佳魁資訊