對JavaScript進行單元測試的工具
簡介
單元測試關(guān)注的是驗證一個模塊或一段代碼的執(zhí)行效果是否和設(shè)計或預(yù)期一樣。有些開發(fā)人員認為,編寫測試用例浪費時間而寧愿去編寫新的模塊。然而,在處理大型應(yīng)用程序時,單元測試實際上會節(jié)省時間;它能幫助您跟蹤問題并安全地更新代碼。
常用縮略語
DOM:文檔對象模型
HTML:超文本標(biāo)記語言
JSTD:JSTestDriver
YUI:Yahoo! User Interface
在過去,只對服務(wù)器端語言進行單元測試。但前端組件越來越復(fù)雜,使得編寫 JavaScript 代碼測試用例的需求日益提高。如果您不經(jīng)常編寫客戶端腳本的測試,學(xué)習(xí)進度可能非常難。測試用戶界面可能需要在思路上做一些調(diào)整。(有些程序開發(fā)人員一時半會還不能相信 JavaScript 是合適的編程語言。)
在本文中,您將學(xué)習(xí)如何使用 QUnit、YUI Test 和 JSTestDriver 對 JavaScript 進行單元測試。
下載 本文的源代碼。
JavaScript 單元測試
為了演示 JavaScript 測試,這一節(jié)將分析 JavaScript 中一個基本函數(shù)測試用例。清單 1 顯示了要測試的函數(shù):將 3(作為一個數(shù))添加到傳遞的變量中。
清單 1. 源代碼 (example1/script.js)
- function addThreeToNumber(el){
- return el + 3;
- }
清單 2 在自執(zhí)行的函數(shù)中包含了測試用例。
清單 2.測試用例 (example1/test.js)
- (function testAddThreeToNumber (){
- var a = 5,
- valueExpected= 8;
- if (addThreeToNumber (a) === valueExpected) {
- console.log("Passed!");
- } else {
- console.log("Failed!");
- }
- }());
將 5 傳遞給測試的函數(shù)之后,測試檢查返回值是 8。如果測試成功,就會在一個現(xiàn)有瀏覽器的控制臺中打印出 Passed!;否則就會出現(xiàn) Failed!。如果要運行測試,需要按照以下步驟進行操作:
1. 將兩個腳本文件導(dǎo)入作為測試運行程序的 HTML 頁面中,如清單 3 所示。
2. 在瀏覽器中打開頁面。
清單 3. HTML 頁面 (example1/runner.html)
- <!DOCTYPE html>
- <html>
- <head>
- <meta http-equiv="Content-type" content="text/html; charset=utf-8">
- <title>Example 1</title>
- <script type="text/javascript" src="js/script.js"></script>
- <script type="text/javascript" src="js/test.js"></script>
- </head>
- <body></body>
- </html>
您可以不使用瀏覽器控制臺,而是將結(jié)果打印在頁面或由 alert() 方法生成的彈出窗口中。
斷言是測試用例中的核心元素,用來驗證某一條件是否滿足。例如,在 清單 2 中,addThreeToNumber (a) === valueExpected 就是一個斷言。
如果您擁有很多用例并帶有很多斷言,那么使用框架就會方便很多。下面的內(nèi)容將會重點介紹一些最流行的 JavaScript 單元測試框架:QUnit、YUI Test 和 JSTestDriver。
#p#
QUnit 入門
QUnit 是與 JUnit(Java 編程)類似的單元測試框架,jQuery 團隊用它來對 jQuery 庫進行單元測試。要使用 QUnit,需要按照以下方法:
1. 下載 qunit.css 文件和 qunit.js 文件(參閱 參考資料)。
2. 創(chuàng)建一個 HTML 頁面,其中包含導(dǎo)入剛下載的 CSS 和 JavaScript 文件的特定標(biāo)簽。
清單 4 顯示了適用于 QUnit 的標(biāo)準(zhǔn)的 HTML 運行程序。
清單 4. HTML 運行程序 (qunit/runner.html)
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8" />
- <title>QUnit Test Suite</title>
- <link rel="stylesheet" href="css/qunit.css" type="text/css" media="screen">
- <script type="text/javascript" src="js/lib/qunit.js"></script>
- </head>
- <body>
- <h1 id="qunit-header">QUnit Test Suite</h1>
- <h2 id="qunit-banner"></h2>
- <div id="qunit-testrunner-toolbar"></div>
- <h2 id="qunit-userAgent"></h2>
- <ol id="qunit-tests"></ol>
- <div id="qunit-fixture">test markup</div>
- </body>
- </html>
假設(shè)您擁有兩個函數(shù)分別負責(zé)將溫度從攝氏轉(zhuǎn)換為華氏,并轉(zhuǎn)換回來。清單 5 顯示了執(zhí)行此轉(zhuǎn)換的腳本。
清單 5. 轉(zhuǎn)換 (qunit/js/script.js)
- function convertFromCelsiusToFahrenheit(c){
- var f = c * (9/5) + 32;
- return f;
- }
- function convertFromFahrenheitToCelsius(f){
- var c = (f - 32) * (5/9);
- return c;
- }
清單 6 顯示了各自的測試用例。
清單 6. 測試用例 (qunit/js/test.js)
- module ("Temperature conversion")
- test("conversion to F", function(){
- var actual1 = convertFromCelsiusToFahrenheit(20);
- equal(actual1, 68, ?Value not correct?);
- var actual2 = convertFromCelsiusToFahrenheit(30);
- equal(actual2, 86, ?Value not correct?);
- })
- test("conversion to C", function(){
- var actual1 = convertFromFahrenheitToCelsius(68);
- equal(actual1, 20, ?Value not correct?);
- var actual2 = convertFromFahrenheitToCelsius(86);
- equal(actual2, 30, ?Value not correct?);
- })
QUnit 中的測試用例由 test() 方法定義。邏輯是包含在傳入函數(shù)的第二個參數(shù)中。在清單 6 中,兩個測試分別名為 conversion to F和 conversion to C。每個測試包含兩個斷言。該測試中的斷言使用了 equal() 方法。equal() 函數(shù)可以將預(yù)期值與測試函數(shù)的實際值相比較。 equal() 方法中的第三個參數(shù)是錯誤情況下顯示的消息。
還可以通過 module() 函數(shù)將測試組織到模塊中。在清單 6 中,Temperature conversion 模塊含有這兩個測試。
如果要運行測試:
1. 在 HTML 運行程序中包含源代碼和測試文件,如清單 7 所示。
2. 在瀏覽器中打開頁面。
清單 7. 在運行程序中包含 script.js 和 test.js
- ...<script type="text/javascript" src="js/script.js"></script>
- <script type="text/javascript" src="js/test.js"></script>
- ...
圖 1 顯示了 QUnit 如何在瀏覽器 (Firefox) 中顯示結(jié)果。
圖 1. QUnit 結(jié)果
清單 6 中的斷言使用了 equal() 方法,但它不是 QUnit 提供的惟一斷言。QUnit 提供的其他斷言包括 ok() 或 strictEqual()。清單 8 顯示了正在執(zhí)行的方法。
#p#
清單 8. 更多的斷言
- module ("Other assertion");
- test("assertions", function(){
- ok(true);
- ok(3);
- strictEqual("c", "c");
- equal (3, "3");
- });
ok() 函數(shù)檢查第一個參數(shù)為 true;strictEqual() 驗證第一個參數(shù)嚴格等于第二個參數(shù)。在這些代碼背后,strictEqual() 使用了=== 運算符,equal() 使用了 == 運算符。
如果測試失敗,QUnit 還提供了有用的信息。將清單 8 中的代碼改成清單 9 中的代碼,讓上一次斷言執(zhí)行失敗。
清單 9. 上一次斷言出現(xiàn)的錯誤
- module ("Other assertion");
- test("assertions", function(){
- ok(true);
- ok(3);
- strictEqual("c", "c");
- strictEqual (3, "3");
- });
圖 2 顯示了 QUnit 執(zhí)行清單 9 代碼所返回的結(jié)果。
圖 2. QUnit 結(jié)果:上次測試失敗
結(jié)果非常詳細,而且很容易查到上次斷言的預(yù)期值與實際值有什么不同。
QUnit 另一項特性能讓您在模塊中的所有測試執(zhí)行之前或之后執(zhí)行命令。module() 函數(shù)接受 setup() 和 teardown() 回調(diào)作為第二個參數(shù)。使用 setup() 函數(shù)更新 清單 6 ,如清單 10 所示。
清單 10. setup() (qunit/js/test-setup.js)
- module ("Temperature conversion", {
- setup : function() {
- this.celsius1 = 20;
- this.celsius2 = 30;
- this.fahrenheit1 = 68;
- this.fahrenheit2 = 86;
- }
- });
- test("conversion to F", function(){
- var actual1 = convertFromCelsiusToFahrenheit(this.celsius1);
- equal(actual1, this.fahrenheit1);
- var actual2 = convertFromCelsiusToFahrenheit(this.celsius2);
- equal(actual2, this.fahrenheit2);
- });
- test("conversion to C", function(){
- var actual1 = convertFromFahrenheitToCelsius(this.fahrenheit1);
- equal(actual1, this.celsius1);
- var actual2 = convertFromFahrenheitToCelsius(this.fahrenheit2);
- equal(actual2, this.celsius2);
- });
該示例移動了設(shè)置部分的斷言所使用的值,以避免在測試的邏輯中使用這些值。
QUnit 還通過 asyncTest() 函數(shù)提供對異步測試的支持,如果您使用 Asynchronous JavaScript and XML (Ajax),這是非常有用的特性。在這樣的環(huán)境中,expect() 函數(shù)可以讓你輕松地驗證測試中運行的斷言數(shù)量。
YUI Test 是 YUI 庫(Yahoo!)的一個組件,是一個可擴展而完整的單元測試框架。如果要使用 YUI Test,需要:
1. 將 YUI 導(dǎo)入 HTML 運行程序,如下所示。
- <script src="http://yui.yahooapis.com/3.4.1/build/yui/yui-min.js"></script>
如以上代碼所示,此樣例使用了 YUI Test 第 3 版本。
2. 在測試腳本文件中,實例化 YUI 函數(shù)。加載所需的模塊,test 和 console,如清單 11 所示。
#p#
清單 11.下載 test 和 console YUI 模塊
- YUI().use("test", "console", function (Y) {
- // Test cases go here
- });
test 模塊顯然是用于測試的。console 模塊并不是強制性的,但本示例將用它來打印結(jié)果。測試用例將會進入回調(diào)中,并以全局的 Y實例作為參數(shù)。
YUI Test 使用 Y.Test.Case() 構(gòu)造函數(shù)實例化新測試用例,使用 Y.Test.Suite() 構(gòu)造函數(shù)來實例化測試套件。測試套件與 JUnit 類似,包含若干個測試用例。可以使用 add() 方法將測試用例添加到測試套件中。
我們使用 YUI test 重新測試 清單 5 中的源代碼。清單 12 顯示了如何創(chuàng)建測試用的套件和測試用例。
清單 12. 測試套件和用例
- YUI().use("test", "console", function (Y) {
- var suite = new Y.Test.Suite("Temperature conversion suite");
- //add a test case
- suite.add(new Y.Test.Case({
- name:"Temperature conversion?
- ));
- });
清單 12 生成了一個名為 Temperature conversion suite 的套件和一個名為 Temperature conversion 的測試用例。現(xiàn)在,可以將測試方法寫入對象文本中,作為參數(shù)傳遞給 Y.Test.Case 構(gòu)造函數(shù),如清單 13 所示。
清單 13. 測試用例與測試方法
- suite.add(new Y.Test.Case({
- name:"Temperature conversion",
- setUp : function () {
- this.celsius1 = 20;
- this.celsius2 = 30;
- this.fahrenheit1 = 68;
- this.fahrenheit2 = 86;
- },
- testConversionCtoF: function () {
- Y.Assert.areEqual(this.fahrenheit1,
- convertFromCelsiusToFahrenheit(this.celsius1));
- Y.Assert.areEqual(this.fahrenheit2,
- convertFromCelsiusToFahrenheit(this.celsius2));
- },
- testConversionFtoC: function () {
- Y.Assert.areEqual(this.celsius1,
- convertFromFahrenheitToCelsius(this.fahrenheit1));
- Y.Assert.areEqual(this.celsius2,
- convertFromFahrenheitToCelsius(this.fahrenheit2));
- }
- }));
您可能注意到,在清單 13 中:
1. 可使用 setUp() 方法。YUI Test 在測試用例和測試套件層提供了 setUp() 和 tearDown() 方法。
2. 測試方法名以 test 單詞開頭,它們包含斷言。
3. 本示例使用 Y.Assert.areEqual() 斷言類型,它與 QUnit 中的 equal() 函數(shù)類似。YUI Test 為斷言提供了多種方法,如:
1). Y.Assert.areSame(),它類似于 QUnit 中的 strictEqual()。
2). 數(shù)據(jù)類型斷言(Y.Assert.isArray()、Y.Assert.isBoolean()、Y.Assert.isNumber() 等等)。
3). 特殊值斷言(Y.Assert.isFalse()、Y.Assert.isNaN()、Y.Assert.isNull() 等等)。
要啟動 YUI 中的測試,需要使用 Y.Test.Runner 對象。還需要將套件或測試用例添加到對象中,然后調(diào)用 run() 方法來運行測試。清單 14 顯示了如何運行 清單 13 中創(chuàng)建的測試。
清單 14. 運行 YUI test
- Y.Test.Runner.add(suite);
- Y.Test.Runner.run();
在默認情況下,結(jié)果會打印在瀏覽器的控制臺中(如果瀏覽器支持控制臺的話)。更好的方法是使用 Yahoo! Console 組件來打印結(jié)果。如果要使用 Yahoo! Console 組件,需要采用 Y.Console 構(gòu)造函數(shù)將控制臺綁定到 HTML 運行程序的 DOM 元素中,如清單 15 所示。
#p#
清單 15. Yahoo! Console
- var console = new Y.Console({
- verbose: true,
- newestOnTop: false,
- width:"600px"
- });
- console.render('#testLogger');
清單 15 顯示了如何使用幾個參數(shù)配置控制臺。該控制臺會在 DOM 元素內(nèi)部呈現(xiàn),其 id 為 testLogger。
需要更新 HTML 運行程序。添加該控制臺所引用的 DOM 元素,如清單 16 所示。
- <body>
- <div id="testLogger"></div>
- </body>
本例為 <body> 設(shè)置了一個類,名為 yui3-skin-sam。該類負責(zé)定義控制臺的皮膚。
圖 3 顯示了運行測試之后的控制臺。
圖 3. YUI Test 結(jié)果
使用 JSTestDriver 輕松測試
通過使用功能強大的 JSTestDriver (JSTD) 工具,您能夠在多個瀏覽器中從命令行運行 JavaScript。JSTD 帶有一個 JAR 文件,它可以讓您啟動服務(wù)器、捕獲一或多個瀏覽器并在這些瀏覽器中運行測試。因為擁有上述的兩個框架,您不需要 HTML 運行程序,但您需要一個配置文件。圖 17 顯示了配置文件。
清單 17. 配置文件 (jsTestDriver.conf)
- server: http://localhost:4224
- load:
- - js/src/*.js
- test:
- - js/test/*.js
該配置文件是用 YAML 編寫的,這是一種很好的配置文件格式。它包含了要啟動的服務(wù)器以及源代碼和測試文件的位置信息。
要使用 JSTD 來執(zhí)行測試:
1. 啟動測試服務(wù)器。從命令行中,進入到保存 jsTestDriver.jar 的文件夾,并執(zhí)行以下命令:
- java -jar JsTestDriver-1.3.3d.jar -port 4224
清單 17 中指定的端口應(yīng)該與配置文件中指定的一樣。在默認情況下,JSTD 會在 JAR 文件所在的同一個目錄下查找 jsTestDriver.conf 文件。
2. 在測試中,通過將 URL http://localhost:4224/capture 復(fù)制粘貼到測試中的瀏覽器,在服務(wù)器上注冊一個或多個瀏覽器。
測試之前示例中所使用的相同代碼(清單 5),但這次使用 JSTD 語法。清單 18 顯示了如何轉(zhuǎn)換 清單 10 的 QUnit 和 清單14 的 YUI Test 中的代碼。
清單 18. JSTD 測試
- TestCase("Temperature conversion", {
- setUp : function () {
- this.celsius1 = 20;
- this.celsius2 = 30;
- this.fahrenheit1 = 68;
- this.fahrenheit2 = 86;
- },
- testConversionCtoF: function () {
- assertSame(this.fahrenheit1, convertFromCelsiusToFahrenheit(this.celsius1));
- assertSame(this.fahrenheit2, convertFromCelsiusToFahrenheit(this.celsius2));
- },
- testConversionFtoC: function () {
- assertSame(this.celsius1, convertFromFahrenheitToCelsius(this.fahrenheit1));
- assertSame(this.celsius2, convertFromFahrenheitToCelsius(this.fahrenheit2));
- }
- });
清單 18 中的代碼與 YUI 版本差別不大。JSTD 使用 TestCase() 函數(shù)來定義測試用例。您可以使用內(nèi)聯(lián)聲明來定義測試方法,如清單 18 所示,或者可以擴展 TestCase 實例的原型。每個測試用例還可以使用 SetUp() 和 tearDown() 方法。
如果要運行測試,運行以下命令:
- java -jar JsTestDriver-1.3.3d.jar --tests all
圖 4 顯示了終端上的輸出結(jié)果。
圖 4. JSTD 測試的結(jié)果
測試會傳入之前捕獲到的所有瀏覽器(Chrome 15、Safari 5 和 Firefox 7)。
JSTD 還能與您偏好的連續(xù)集成系統(tǒng)很好地集成,成為連續(xù)版本的一部分。它還能與 IDE 集成,如 Eclipse(插件)或 TextMate(包)。
結(jié)束語
隨著現(xiàn)在對 Web 應(yīng)用程序客戶端的關(guān)注,對 JavaScript 進行單元測試就顯得尤為必要。有很多框架可以幫助您完成此任務(wù),本文介紹了三個最流行的框架:QUnit、YUI Test 和 JSTestDriver。
QUnit 非常簡單,很適合初學(xué)者的框架。
YUI Test 是個全面的工具,適合熟悉 YUI 庫的用戶。
JSTestDriver 可在多個瀏覽器中運行測試。