文章目錄
本篇我們將通過對Date.strftime編寫單元測試的例子,學會斷言、測試案例函數的相關知識。
首先我們先來看Date.strftime的實現代碼。
Date.prototype.strftime = (function () { function strftime(format) { var date = this; return (format + "").replace(/%([a-zA-Z])/g, function (m, f) { var formatter = Date.formats && Date.formats[f]; if (typeof formatter == "function") { return formatter.call(Date.formats, date); } else if (typeof formatter == "string") { return date.strftime(formatter); } return f; }); } // 內部協助函數 function zeroPad(num) { return (+num < 10 ? "0" : "") + num; } Date.formats = { // Formatting 方法 d: function (date) { return zeroPad(date.getDate()); }, m: function (date) { return zeroPad(date.getMonth() + 1); }, y: function (date) { return zeroPad(date.getYear() % 100); }, Y: function (date) { return date.getFullYear(); }, // Format 速記方式 F: "%Y-%m-%d", D: "%m/%d/%y" }; return strftime;}());
Date.prototype.strftime函數整體是一個即時匿名函數,該函數會自動執行,把strftime函數作為結果返回。strftime()通過正則表的式判斷格式化標示符,返回正確的結果。 zeroPad()在1-9的資料前加0 。Date.formats 對象提供一系列輔助方法,用來處理時間。
如果代碼有哪裡看不懂可以給我留言,我會盡量講解。
斷言
單元測試的核心是斷言,通過它的自動運行來比較開發人員對於系統的預期結果是否和執行結果一致。兩者一致則說明我們的系統一切正常,否則就存在問題,需要我們進一步確認。我們先來看一個簡單的斷言函數:
function assert(message, expr) { if (!expr) { throw new Error(message); } assert.count++; return true;}assert.count = 0;
上面的函數只是簡單的驗證第二個參數是否是真值(除了false, null, undefined, 0, "", 和 NaN這些值之外的都可以)。斷言成功,assert.count會加1,失敗則拋出異常。
我們可以使用他來測試strftime() 方法。
var date = new Date(2009, 9, 2);try { assert("%Y should return full year", date.strftime("%Y") === "2009"); assert("%m should return month", date.strftime("%m") === "10"); assert("%d should return date", date.strftime("%d") === "02"); assert("%y should return year as two digits", date.strftime("%y") === "09"); assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02"); console.log(assert.count + " tests OK");} catch (e) { console.log("Test failed: " + e.message);}
在自動化測試中,我們經常使用綠色代表成功,紅色代表失敗。我們可以建立一個output方法輸出不同顏色的資訊條,代替console.log的功能。
function output(text, color) { var p = document.createElement("p"); p.innerHTML = text; p.style.color = color; document.body.appendChild(p);}// 可以用它來代替上面斷言例子中的 console.logoutput(assert.count + " tests OK", "#0c0");// 失敗的時候使用下面的方法:output("Test failed: " + e.message, "#c00");
測試案例函數
在建立斷言函數之後,我們需要建立測試案例函數,以便把斷言組織起來。測試案例裡麵包含很多子的測試方法,每個測試方法針對被測對象的一部分行為進行測試。下面我們看一個簡單的測試案例函數的實現,其中name是測試案例的名稱,tests是一組測試方法,其中每個測試方法以‘test’開頭。
function testCase(name, tests) { assert.count = 0; var successful = 0; var testCount = 0;
for (var test in tests) { if (!/^test/.test(test)) { continue; }
testCount++;
try { tests[test](); output(test, "#0c0"); successful++; } catch (e) { output(test + " failed: " + e.message, "#c00"); } }
var color = successful == testCount ? "#0c0" : "#c00"; output("<strong>" + testCount + " tests, " + (testCount - successful) + " failures</strong>", color);}
下面我們使用上面的方法測試strftime()。
var date = new Date(2009, 9, 2);testCase("strftime test", { "test format specifier %Y": function () { assert("%Y should return full year", date.strftime("%Y") === "2009"); }, "test format specifier %m": function () { assert("%m should return month", date.strftime("%m") === "10"); }, "test format specifier %d": function () { assert("%d should return date", date.strftime("%d") === "02"); }, "test format specifier %y": function () { assert("%y should return year as two digits", date.strftime("%y") === "09"); }, "test format shorthand %F": function () { assert("%F should act as %Y-%m-%d", date.strftime("%F") === "2009-10-02"); }});
這組測試方法針對strftime()不同功能點進行測試,組合起來實現了對其全部功能的測試,最後把測試結果展現給使用者。可以說效果還是相當不錯的。
Setup 和 Teardown
接下來我們介紹setup()和teardown()方法的實現。這兩個方法分別在tests執行之前和之後運行,setup()可以用來初始化測試條件,teardown()可以用來銷毀相關條件。下面我們完善之前的testCase()方法,添加對setup和teardown()的支援。
function testCase(name, tests) { assert.count = 0; var successful = 0; var testCount = 0; var hasSetup = typeof tests.setUp == "function"; var hasTeardown = typeof tests.tearDown == "function"; for (var test in tests) { if (!/^test/.test(test)) { continue; } testCount++; try { if (hasSetup) { tests.setUp(); } tests[test](); output(test, "#0c0"); if (hasTeardown) { tests.tearDown(); } successful++; } catch (e) { output(test + " failed: " + e.message, "#c00"); } } var color = successful == testCount ? "#0c0" : "#c00"; output("<strong>" + testCount + " tests, " + (testCount - successful) + " failures</strong>", color);}
下面我們用改進後的testCase()重新測試strftime()方法。
testCase("strftime test", { setUp: function () { this.date = new Date(2009, 9, 2, 22, 14, 45); }, "test format specifier Y": function () { assert("%Y should return full year", this.date.strftime("%Y") == 2009); }, // ...});
總結
本文提供的斷言和測試案例函數比較簡單,相信大家一看就會,其他複雜的測試架構則要相對複雜的多。但是他們在基本原理上沒有太大差別,只是代碼實現方式不同,再者就是api提供的更多些。本文主要目地在於拋磚引玉,向大家介紹測試架構中相關方法大致的實現方式,以及如何使用測試架構進行單元測試。要想更好的精通單元測試,除了繼續加強理論知識的學習、不斷的實踐之外,還可以看看其他架構的原始碼,相信對你定會有比較大的協助。