高效 JavaScript 單元測試

fmms 13年前發布 | 39K 次閱讀 Java開發 JavaScript

        一個損壞的 JavaScript 代碼示例

        Web 應用程序面臨的一個最大挑戰是支持不同版本的 Web 瀏覽器。能在 Safari 上運行的 JavaScript 代碼不一定能在 Windows® Internet Explorer (IE)、Firefox 或 Google Chrome 上運行。這個挑戰的根源是呈現層中的 JavaScript 代碼從一開始就沒有進行測試。如果沒有對代碼進行單元測試,那么在升級或支持新瀏覽器后,組織可能需要花錢反復測試 Web 應用程序。本文將展示如何通過高效的 JavaScript 代碼單元測試降低測試成本。

        一個常見用例是登錄表單 JavaScript 驗證。考慮清單 1 中的表單。

        清單 1. 登錄表單

<FORM>
    <table>
        <tr>
            <td>Username</td>
            <td><input type="text" id="username"/></td>
            <td><span id="usernameMessage"></span></td>
        </tr>
        <tr>
            <td>Password</td>
            <td><input type="password" id="password"/></td>
            <td><span id="passwordMessage"></span></td>
        </tr>    
        <tr>
            <td><input type="button" onclick="new appnamespace.
            ApplicationUtil () .validateLoginForm ()" value="Submit"/></td>
        </tr>
    </table>
</FORM>

        這個表單很簡單,僅包含用戶名和密碼字段。單擊提交按鈕時,將通過 ApplicationUtil 執行一個特定的表單驗證。以下是負責驗證 HTML 表單的 JavaScript 對象。清單 2 顯示了 ApplicationUtil 對象的代碼。

        清單 2. 損壞的 ApplicationUtil 對象代碼

appnamespace = {};

appnamespace.ApplicationUtil = function() {};

appnamespace.ApplicationUtil.prototype.validateLoginForm =  function(){
    var error = true;
    document.getElementById ("usernameMessage") .innerText = "";
    document.getElementById ("passwordMessage") .innerText = "";    

    if (!document.getElementById ("username") .value) {
        document.getElementById ("usernameMessage") .innerText = 
        "This field is required";
        error = false;
    }

    if (!document.getElementById ("password") .value) {
        document.getElementById ("passwordMessage") .innerText = 
        "This field is required";
        error = false;
    }        

    return error;        
};

        在清單 2 中,ApplicationUtil 對象提供一個簡單驗證:用戶名和密碼字段都已填充。如果某個字段為空,就會顯示一條錯誤消息:This field is required

        上面的代碼能夠在 Internet Explorer 8 和 Safari 5.1 上工作,但無法在 Firefox 3.6 上工作,原因是 Firefox 不支持 innerText 屬性。通常,(上述代碼和其他類似 JavaScript 代碼中的)主要問題是不容易發現編寫的 JavaScript 代碼是不是跨瀏覽器兼容的。

        這個問題的一個解決方案是進行自動化單元測試,檢查代碼是不是跨瀏覽器兼容。

        JsTestDriver

        JsTestDriver library 是最好的 JavaScript 單元測試框架之一,它為 JavaScript 代碼提供了跨瀏覽器測試。圖 1 展示了 JsTestDriver 的架構。

        圖 1. JsTestDriver 架構

高效 JavaScript 單元測試

        捕獲不同的瀏覽器之后,服務器會負責將 JavaScript 測試用例運行程序代碼加載到瀏覽器中。可以通過命令行捕獲瀏覽器,也可以通過將瀏覽器指向服務器 URL 來捕獲瀏覽器。一旦捕獲到瀏覽器,該瀏覽器就被稱為從屬瀏覽器。服務器可以加載 JavaScript 代碼,在每個瀏覽器上執行測試用例,然后將結果返回給客戶端。

        客戶端(命令行)需要以下兩個主要項目:

  1. JavaScript 文件,即源文件和測試文件
  2. 配置文件,用于組織源文件和測試文件的加載

        這個架構比較靈活,允許單個服務器從網絡中的其他機器捕獲任意數量的瀏覽器。例如,如果您的代碼在 Linux 上運行但您想針對另一個 Windows 機器上的 Microsoft Internet Explorer 運行您的測試用例,那么這個架構很有用。

        要使用 JsTestDriver 庫,請先下載最新版的 JsTestDriver 1.3.2

jsTestDriver 是開源項目

jsTestDriver 是 Apache 2.0 許可 下的一個開源項目,托管在 Google Code 上,后者是一個類似于 SourceForge 的項目存儲庫。只要使用 Open Source Initiative 批準的 許可,開發人員就能在這個存儲庫中創建和管理公共項目。

還有許多其他 JavaScript 單元測試工具,請參見下面的 參考資料 部分中的其他工具,比如 Dojo Objective Harness (DOH)。

        編寫單元測試代碼

        現在開始編寫 JavaScript 測試用例。為簡單起見,我將測試以下用例:

  • 用戶名和密碼字段均為空。
  • 用戶名為空,密碼不為空。
  • 用戶名不為空,密碼為空。

        清單 3 顯示了表示 TestCase 對象的 ApplicationUtilTest 對象的部分代碼。

        清單 3. ApplicationUtilTest 對象代碼的一部分

ApplicationUtilTest = TestCase ("ApplicationUtilTest");

ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};

ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
    var applicationUtil = new appnamespace.ApplicationUtil ();

    /* Simulate empty user name and password */
    document.getElementById ("username") .value = "";
    document.getElementById ("password") .value = "";    
    applicationUtil.validateLoginForm ();
    assertEquals ("Username is not validated correctly!", "This field is required", 
    document.getElementById ("usernameMessage") .innerHTML);
    assertEquals ("Password is not validated correctly!", "This field is required", 
    document.getElementById ("passwordMessage") .innerHTML);    
};

        ApplicationUtilTest 對象通過 JsTestDriver TestCase 對象創建。如果您熟悉 JUnit 框架,那么您肯定熟悉 setUptestXXX 方法。setUp 方法用于初始化測試用例。對于本例,我使用該方法來聲明一個 HTML 片段,該片段將用于其他測試用例方法。

        DOC 注釋是一個 JsTestDriver 慣用語,可以用于輕松聲明一個 HTML 片段。

        在 testValidateLoginFormBothEmpty 方法中,創建了一個 ApplicationUtil 對象,并在測試用例方法中使用該對象。然后,代碼通過檢索用戶名和密碼的 DOM 元素并將它們的值設置為空值來模擬輸入空用戶名和密碼。可以調用 validateLoginForm 方法來執行實際表單驗證。最后,將調用 assertEquals 來確保 usernameMessagepasswordMessage span 元素中的消息是正確的,即:This field is required

        在 JsTestDriver 中,可以使用以下構件:

  • fail ("msg"):表明測試一定會失敗,消息參數將顯示為一條錯誤消息。
  • assertTrue ("msg", actual):斷定實際參數正確。否則,消息參數將顯示為一條錯誤消息。
  • assertFalse ("msg", actual):斷定實際參數錯誤。否則,消息參數將顯示為一條錯誤消息。
  • assertSame ("msg", expected, actual):斷定實際參數與預期參數相同。否則,消息參數將顯示為一條錯誤消息。
  • assertNotSame ("msg", expected, actual):斷定實際參數與預期參數不相同。否則,消息參數將顯示為一條錯誤消息。
  • assertNull ("msg", actual):斷定參數為空。否則,消息參數將顯示為一條錯誤消息。
  • assertNotNull ("msg", actual):斷定實際參數不為空。否則,消息參數將顯示為一條錯誤消息。

        其他方法的代碼包含其他測試用例。清單 4 顯示了測試用例對象的完整代碼。

        清單 4. ApplicationUtil 對象完整代碼

ApplicationUtilTest = TestCase ("ApplicationUtilTest");

ApplicationUtilTest.prototype.setUp = function () {
/*:DOC += <FORM action=""><table><tr><td>Username</td><td>
<input type="text" id="username"/></td><td><span id="usernameMessage">
</span></td></tr><tr><td>Password</td><td>
<input type="password" id="password"/></td><td><span id="passwordMessage"
></span></td></tr></table></FORM>*/
};

ApplicationUtilTest.prototype.testValidateLoginFormBothEmpty = function () {
    var applicationUtil = new appnamespace.ApplicationUtil ();

    /* Simulate empty user name and password */
    document.getElementById ("username") .value = "";
    document.getElementById ("password") .value = "";    

    applicationUtil.validateLoginForm ();

    assertEquals ("Username is not validated correctly!", "This field is required", 
    document.getElementById ("usernameMessage") .innerHTML);
    assertEquals ("Password is not validated correctly!", "This field is required", 
    document.getElementById ("passwordMessage") .innerHTML);    
};

ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyUserName = function () {
    var applicationUtil = new appnamespace.ApplicationUtil ();

    /* Simulate empty user name and password */
    document.getElementById ("username") .value = "";
    document.getElementById ("password") .value = "anyPassword";    

    applicationUtil.validateLoginForm ();

    assertEquals ("Username is not validated correctly!", 
    "This field is required", document.getElementById ("usernameMessage") .innerHTML);
    assertEquals ("Password is not validated correctly!", 
    "", document.getElementById ("passwordMessage") .innerHTML);    
};

ApplicationUtilTest.prototype.testValidateLoginFormWithEmptyPassword = function () {
    var applicationUtil = new appnamespace.ApplicationUtil ();

    document.getElementById ("username") .value = "anyUserName";
    document.getElementById ("password") .value = "";    

    applicationUtil.validateLoginForm ();

    assertEquals ("Username is not validated correctly!", 
    "", document.getElementById ("usernameMessage") .innerHTML);
    assertEquals ("Password is not validated correctly!", 
    "This field is required", document.getElementById ("passwordMessage").
    innerHTML);    
};

        配置用于測試的不同瀏覽器

        測試 JavaScript 代碼的一個推薦實踐是將 JavaScript 源代碼和測試代碼放置在不同的文件夾中。對于圖 2 中的示例,我將 JavaScript 源文件夾命名為 "js-src",將 JavaScript 測試文件夾命名為 "js-test",它們都位于 "js" 父文件夾下。

        圖 2. JavaScript 測試文件夾結構

高效 JavaScript 單元測試

        組織好源和測試文件夾后,必須提供配置文件。默認情況下,JsTestDriver 運行程序會尋找名為 jsTestDriver.conf 的配置文件。您可以從命令行更改配置文件名稱。清單 5 顯示了 JsTestDriver 配置文件的內容。

        清單 5. JsTestDriver 配置文件內容

server: http://localhost:9876  load:
  - js-src/*.js
  - js-test/*.js

        配置文件采用 YAML 格式。server 指令指定測試服務器的地址,load 指令指出了將哪些 JavaScript 文件加載到瀏覽器中以及加載它們的順序。

        現在,我們將在 IE、Firefox 和 Safari 瀏覽器上運行測試用例類。

        要運行測試用例類,需要啟動服務器。您可以使用以下命令行啟動 JsTestDriver 服務器:

java -jar JsTestDriver-1.3.2.jar --port 9876 --browser "[Firefox Path]",
          "[IE Path]","[Safari Path]"

        使用這個命令行,服務器將以 Port 9876 啟動,捕獲您的機器上的 Firefox、IE 和 Safari 瀏覽器。

        啟動并捕獲瀏覽器后,可以通過以下命令行運行測試用例類:

java -jar JsTestDriver-1.3.2.jar --tests all

        運行命令后,您將看到第一輪結果,如清單 6 所示。

        清單 6. 第一輪結果

Total 9 tests (Passed: 6; Fails: 3; Errors: 0) (16.00 ms)
  Firefox 3.6.18 Windows: Run 3 tests (Passed: 0; Fails: 3; Errors 0) (8.00 ms)
    ApplicationUtilTest.testValidateLoginFormBothEmpty failed (3.00 ms): 
    AssertError: Username is not validated correctly! expected "This field 
    is required" but was "" Error ("Username is not validated correctly! 
    expected \"This field is required\" but was \"\"")@:0()@http://localhost     :9876/test/js-test/TestApplicationUtil.js:16

    ApplicationUtilTest.testValidateLoginFormWithEmptyUserName failed (3.00 ms): 
    AssertError: Username is not validated correctly! expected "This field is 
    required" but was "" Error ("Username is not validated correctly! expected 
    \"This field is required\" but was \"\"")@:0()@http://localhost:9876/test     /js-test/TestApplicationUtil.js:29

    ApplicationUtilTest.testValidateLoginFormWithEmptyPassword failed (2.00 ms): 
    AssertError: Password is not validated correctly! expected "This field is 
    required" but was "" Error ("Password is not validated correctly! expected 
    \"This field is required\" but was \"\"")@:0()@http://localhost:9876/test/     js-test/TestApplicationUtil.js:42

  Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (2.00 ms)
  Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; 
  Errors 0) (16.00 ms)
Tests failed: Tests failed. See log for details.

        注意,在清單 6 中,主要問題出在 Firefox 上。測試在 Internet Explorer 和 Safari 上均可順利運行。

        修復 JavaScript 代碼并重新運行測試用例

        我們來修復損壞的 JavaScript 代碼。我們將使用 innerHTML 替代 innerText。清單 7 顯示了修復后的 ApplicationUtil 對象代碼。

        清單 7. 修復后的 ApplicationUtil 對象代碼

appnamespace = {};

appnamespace.ApplicationUtil = function() {};

appnamespace.ApplicationUtil.prototype.validateLoginForm =  function(){
    var error = true;
    document.getElementById ("usernameMessage") .innerHTML = "";
    document.getElementById ("passwordMessage") .innerHTML = "";    

    if (!document.getElementById ("username") .value) {
        document.getElementById ("usernameMessage") .innerHTML = 
        "This field is required";
        error = false;
    }

    if (!document.getElementById ("password") .value) {
        document.getElementById ("passwordMessage") .innerHTML = 
        "This field is required";
        error = false;
    }        

    return error;        
};

        使用 --test all 命令行參數重新運行測試用例對象。清單 8 顯示了第二輪運行結果。

        清單 8. 第二輪運行結果

Total 9 tests (Passed: 9; Fails: 0; Errors: 0) (9.00 ms)
  Firefox 3.6.18 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (9.00 ms)
  Safari 534.50 Windows: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
  Microsoft Internet Explorer 8.0 Windows: Run 3 tests (Passed: 3; Fails: 0; 
Errors 0) 
  (0.00 ms)

        如清單 8 所示,JavaScript 代碼現在在 IE、Firefox 和 Safari 上都能正常運行。

        結束語

        在本文中,您了解了如何使用一個最強大的 JavaScript 單元測試工具 (JsTestDriver) 在不同的瀏覽器上測試 JavaScript 應用程序代碼。還了解了什么是 JsTestDriver,如何配置它,以及如何在 Web 應用程序中使用它來確保應用程序的 JavaScript 代碼的質量和可靠性。

        下載

描述 名字 大小 下載方法
源代碼 simple.zip 3. 35MB HTTP

        關于下載方法的信息

        參考資料

        學習

  • 訪問 JUnit.org,了解如何使用 JUnit 測試框架。
  • 詳細了解 YAML,這是一個針對所有編程語言的人類友好的數據序列化標準。
  • 訪問 developerWorks Open source 專區獲得豐富的 how-to 信息、工具和項目更新以及最受歡迎的文章和教程,幫助您用開放源碼技術進行開發,并將它們與 IBM 產品結合使用。

        獲得產品和技術

        討論

        關于作者

        Hazem Saleh 有 6 年的 JEE 和開源技術經驗。他致力于 Apache MyFaces 方面的工作,是 MyFaces 項目許多組件的發起人,比如 Tomahawk CAPTCHA、Commons ExportActionListener、Media、PasswordStrength 等等。他是 GMaps4JSF(一個集成 Google Maps 和 Java ServerFaces 的集成項目)和 Mashups4JSF(集成 mashup 服務和 JavaServer Faces 的集成項目)的創始人,是《The Definitive Guide to Apache MyFaces and Facelets (Apress)》和其他許多 JSF 文章的作者,并且是 developerworks 的投稿人和 JSF 演講家。他現在是 IBM Egypt 的資深軟件工程師和 Web 2.0 技術的主題專家。

來自: www.ibm.com

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!