使用 Jasmine 進行測試驅動的 JavaScript 開發

Jasmine 爲 JavaScript 提供了 TDD (測試驅動開發)的框架,對於前端軟件開發提供了良好的質量保證,這裏對 Jasmine 的配置和使用作一個說明。css

目前,Jasmine 的最新版本是 2.3 版,這裏以 2.3 版進行說明。網上已經有一些關於 Jasmine 的資料,可是,有些資料比較久遠,已經與現有版本不一致。因此,這裏特別以最新版進行說明。html

1. 下載

官網地址:http://jasmine.github.io/前端

官網文檔地址:http://jasmine.github.io/2.3/introduction.htmlnode

下載地址:https://github.com/jasmine/jasmine/releasesgit

在 GitHub 上提供了獨立版本 jasmine-standalone-2.3.4.zip 和源碼版本,若是使用的話,直接使用 standalone 版本便可。github

解壓以後,能夠獲得以下所示的文件結構。正則表達式

其中,lib 中是 Jasmine 的實現文件,在 lib/jasmine-2.3.4 文件夾中,能夠看到以下的文件。express

打開最外層的 SpecRunner.html ,這是一個 Jasmine 的模板,其中提供了測試的示例,咱們能夠在使用中直接套用這個模板。其中的內容爲:數組

複製代碼
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.3.4</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.3.4/jasmine_favicon.png"> <link rel="stylesheet" href="lib/jasmine-2.3.4/jasmine.css"> <script src="lib/jasmine-2.3.4/jasmine.js"></script> <script src="lib/jasmine-2.3.4/jasmine-html.js"></script> <script src="lib/jasmine-2.3.4/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>
複製代碼

能夠看到其中引用了 lib/jasmine-2.3.4/jasmine.js, lib/jasmine-2.3.4/jasmine- html.js 和 lib/jasmine-2.3.4/boot.js 三個系統文件,其中 boot.js 是網頁狀況下的啓動文件,在 張丹 的 jasmine行爲驅動,測試先行 這篇文章中,要寫一個 report.js 的啓動腳本,這裏已經不用了,直接使用 boot.js 就能夠。瀏覽器

頁面下面引用的  src/Player.js 和 src/Song.js 是咱們的測試對象,而 spec/SpecHelper.js 和 spec/PlayerSpec.js 則是兩個對應的測試文件,測試用例就定義在 spec 中。

2. 測試的定義

咱們仍是直接看示例,

一個是 Song.js,這裏定義了一個 Song 的類,經過原型定義了一個persistFavoriteStatus 實例方法,注意,這裏尚未實現,若是調用則會拋出異常。腳本以下。

複製代碼
function Song() { } Song.prototype.persistFavoriteStatus = function(value) { // something complicated throw new Error("not yet implemented"); };
複製代碼

另一個是 player.js,定義了 Player 類,定義了一個歌手,經過原型定義了 play, pause, resume 和 makeFavorite 實例方法。對象有一個 isPlaying 的狀態,其中 resume 尚未完成。

複製代碼
function Player() { } Player.prototype.play = function(song) { this.currentlyPlayingSong = song; this.isPlaying = true; }; Player.prototype.pause = function() { this.isPlaying = false; }; Player.prototype.resume = function() { if (this.isPlaying) { throw new Error("song is already playing"); } this.isPlaying = true; }; Player.prototype.makeFavorite = function() { this.currentlyPlayingSong.persistFavoriteStatus(true); };
複製代碼

下面看測試的定義,具體測試的說明,直接加在註釋中。

複製代碼
describe("Player", function() { var player; var song; beforeEach(function() { player = new Player(); song = new Song(); }); // 檢測正在歌手進行的歌曲確實是指定的歌曲 it("should be able to play a Song", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); //demonstrates use of custom matcher  expect(player).toBePlaying(song); }); // 進行測試的分組,這裏測試暫停狀態 describe("when song has been paused", function() { beforeEach(function() { player.play(song); player.pause(); }); // isPlaying 的狀態檢測 it("should indicate that the song is currently paused", function() { expect(player.isPlaying).toBeFalsy(); // demonstrates use of 'not' with a custom matcher //  expect(player).not.toBePlaying(song); }); // 恢復 it("should be possible to resume", function() { player.resume(); expect(player.isPlaying).toBeTruthy(); expect(player.currentlyPlayingSong).toEqual(song); }); }); // demonstrates use of spies to intercept and test method calls // 使用 spyOn 爲對象建立一個 mock 函數 it("tells the current song if the user has made it a favorite", function() { spyOn(song, 'persistFavoriteStatus'); player.play(song); player.makeFavorite(); expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true); }); //demonstrates use of expected exceptions // 異常檢測 describe("#resume", function() { it("should throw an exception if song is already playing", function() { player.play(song); expect(function() { player.resume(); }).toThrowError("song is already playing"); }); }); });
複製代碼

使用瀏覽器直接打開 SpenRunner.html 看到的結果

能夠看到測試都經過了。

若是咱們將第一個測試 expect(player.currentlyPlayingSong).toEqual(song); 改爲 expect(player.currentlyPlayingSong).toEqual( 1 );

測試通不過,顯示會變成這樣。

 3. 語法

3.1 describe 和 it

describe 用來對測試用例進行分組,分組能夠嵌套,每一個分組能夠有一個描述說明,這個說明將會出如今測試結果的頁面中。

describe("Player", function() { describe("when song has been paused", function() {

而 it 就是測試用例,每一個測試用例有一個字符串的說明,匿名函數內就是測試內容。

  // 檢測正在歌手進行的歌曲確實是指定的歌曲 it("should be able to play a Song", function() { player.play(song); expect(player.currentlyPlayingSong).toEqual(song); });

測試結果的斷言使用 expect 進行,函數內提供測試的值,toXXX 中則是指望的值。

上面的測試使用 toEqual 進行相等斷言判斷。

3.2 beforeEach 和 afterEach

示例中還出現了 beforeEach。

複製代碼
  var player; var song; beforeEach(function() { player = new Player(); song = new Song(); });
複製代碼

顧名思義,它表示在本組的每一個測試以前須要進行的準備工做。在咱們這裏的測試中,總要用到 player 和 song 這兩個對象實例,使用 forEach 保證在每一個測試用例執行以前,從新對這兩個對象進行了初始化。

afterEach 會在每個測試用例執行以後執行。

3.3 自定義的斷言

除了系統定義的 toEqual 等等斷言以外,也可使用自定義的斷言,在上面的示例中就出現了 toBePlaying 斷言。

    //demonstrates use of custom matcher expect(player).toBePlaying(song);

這個自定義的斷言定義在 SpecHelper.js 文件中。

複製代碼
beforeEach(function () { jasmine.addMatchers({ toBePlaying: function () { return { compare: function (actual, expected) { var player = actual; return { pass: player.currentlyPlayingSong === expected && player.isPlaying }; } }; } }); });
複製代碼

其中調用了 jasmine 的 addMatchers 函數進行定義,原來這裏不叫斷言,稱爲 matcher ,也就是匹配器。

斷言是一個函數,返回一個對象,其中有一個 compare 的函數,這個函數接收兩個參數,第一個是實際值,第二個爲指望的值。具體的斷言邏輯本身定義,這裏比較歌手演唱的對象是否爲咱們傳遞的對象,而且歌手的狀態爲正在表演中。

斷言函數須要返回一個對象,對象的 pass 屬性爲一個 boolean 值,表示是否經過。

4. 經常使用斷言

4.1 toEqual

深相等,對於對象來講,會比較對象的每一個屬性。對於數組來講,會比較數組中每一個元素。

複製代碼
  describe("The 'toEqual' matcher", function() { it("works for simple literals and variables", function() { var a = 12; expect(a).toEqual(12); }); it("should work for objects", function() { var foo = { a: 12, b: 34 }; var bar = { a: 12, b: 34 }; expect(foo).toEqual(bar); }); });
複製代碼

 

4.2 toBe

對於對象,引用相等。對於值,值相等。

pass: actual === expected

例如

  it("and has a positive case", function() { expect(true).toBe(true); });

 

4.3 toBeTruthy

是否爲真。

  it("The 'toBeTruthy' matcher is for boolean casting testing", function() { var a, foo = "foo"; expect(foo).toBeTruthy(); expect(a).not.toBeTruthy(); });

 

4.4 toBeFalsy

是否爲假。

  it("The 'toBeFalsy' matcher is for boolean casting testing", function() { var a, foo = "foo"; expect(a).toBeFalsy(); expect(foo).not.toBeFalsy(); });

 

4.5 toBeDefined

是否認義過

  it("creates spies for each requested function", function() { expect(tape.play).toBeDefined(); expect(tape.pause).toBeDefined(); expect(tape.stop).toBeDefined(); expect(tape.rewind).toBeDefined(); });

 

4.6 toBeUndefined

沒有定義

複製代碼
  it("The `toBeUndefined` matcher compares against `undefined`", function() { var a = { foo: "foo" }; expect(a.foo).not.toBeUndefined(); expect(a.bar).toBeUndefined(); });
複製代碼

 

4.7 toBeNull

複製代碼
  it("The 'toBeNull' matcher compares against null", function() { var a = null; var foo = "foo"; expect(null).toBeNull(); expect(a).toBeNull(); expect(foo).not.toBeNull(); });
複製代碼

 

4.9 toBeGreaterThan

複製代碼
  it("The 'toBeGreaterThan' matcher is for mathematical comparisons", function() { var pi = 3.1415926, e = 2.78; expect(pi).toBeGreaterThan(e); expect(e).not.toBeGreaterThan(pi); });
複製代碼

 

4.10 toBeLessThan

複製代碼
  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); });
複製代碼

 

4.11 toBeCloseTo

複製代碼
  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); });
複製代碼

 

4.12 toContain

集合中是否包含。

  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"); });

 

4.13 toMatch

正則表達式的匹配

複製代碼
  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/); });
複製代碼

 

4.14 toThrow

檢測是否拋出異常

複製代碼
  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(); });
複製代碼

 

4.15 toHaveBeenCalled

4.16 toHaveBeenCalledWith

是否調用過。

複製代碼
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() { expect(bar).toBeNull(); }); });
複製代碼

 

RequireJS Jasmine 2.0 編寫測試

http://ju.outofmemory.cn/entry/96587

相關文章
相關標籤/搜索