寫在前面
本文會介紹一些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壓縮包,得到如下的目錄結構:
- 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提供了beforeEach
、afterEach
、beforeAll
、afterAll
方法。
- 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);
});
});
上面代碼在瀏覽器控制臺的輸出
很明顯
- beforeAll和afterAll在兩個it執行前后,總的只執行了一遍;
- beforeEach,afterEach在每次it執行前后,都執行了一遍,所以結果打印了兩遍
beforeEach run
和afterEach 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);
})
})
});
同樣,代碼在瀏覽器控制臺有輸出
自定義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
參考
JavaScript單元測試框架-Jasmine
Javascript測試框架Jasmine(四):自定義Matcher
jasmine測試框架簡介
JavaScript 單元測試框架:Jasmine 初探