Dojo 敏捷開發:集成 DOH 單元測試到 Ant build

jopen 12年前發布 | 47K 次閱讀 單元測試 JavaScript

簡介: DOH 是一種基于 Dojo 技術的 JavaScript 單元測試框架,也是目前主流的 Dojo 單元測試技術。Ant 是基于 Java 技術的構建工具,Ant build 是敏捷開發中用于持續集成的主流方法。本文首先介紹 Dojo 單元測試的類型以及使用 DOH 運行單元測試的方式,然后重點介紹如何將 DOH 編寫的 Dojo 單元測試集成到 Ant build 中,能夠讓單元測試的結果展示在 build 結果中,并且進一步影響 build 的成功或者失敗。

前言

熟悉敏捷開發的讀者都知道,在項目開發過程中創建一個定期運行的 build 是至關重要的,它可以幫助項目團隊及時的發現項目中存在的問題以及查看迭代的結果。通常 build 中都會包含一項很重要的工作就是執行項目中的單元測試并匯報單元測試的執行結果,如果有單元測試失敗那么就讓 build 失敗。對于使用 Dojo 開發的項目而言,如何將 Dojo 代碼的單元測試集成到項目的 build 中是貫徹敏捷開發原則必不可少的一步。

本文將首先簡單對 Dojo 單元測試框架 DOH 進行簡單介紹,然后說明如何將編寫的單元測試集成到 Ant build 中。

DOH 單元測試簡介

DOH (Dojo Objective Harness) 是 Dojo 提供的 JavaScript 測試套件,也是當前最有效的 Dojo 代碼單元測試框架。它不僅可以用于測試 Dojo 代碼,也可以獨立于 Dojo ,用于測試其它 JavaScript 代碼。

在一個用 Dojo 開發的應用中,按照代碼的功能通常可將測試點分為如下幾種類型:JavaScript 代碼邏輯、Ajax 異步通信、widget 的顯示及行為、UI Look & Feel。

  1. JavaScript 代碼邏輯,是指一個功能邏輯相對獨立的 JavaScript 代碼段,通常是一個 JavaScript function 或者一個 Dojo class 的 method。它可以在一個相對封閉的數據集中進行執行,執行的結果是返回一個獨立的數據集或者改變封閉數據集中的數據。對于這種代碼的單元測試是非常容 易編寫和執行的,而且執行時也不會產生任何的副作用。我們只需要構造一個數據集,然后在這個數據集上調用該 JavaScript 代碼段,最后檢驗執行結果是否與預期相符即可。這種單元測試不需要與服務器通信也不需要后端代碼的支持,而且大多不會存在瀏覽器的兼容性問題。
  2. Ajax 異步通信:在使用 Ajax Framework 進行開發的應用系統中,異步請求服務器數據是非常常見的操作,測試 Ajax 很關鍵的是等待服務器返回結果然后對結果進行判斷,DOH 提供了 DOH.Deffered 對象,利用它可以很方便的進行 Ajax 測試。當然這類測試需要服務器端進行支持,也可以在客戶端用數據文件模擬服務器端返回的數據進行測試。
  3. Widget 的顯示及行為:Dojo 的 dijit 庫提供了強大的 Widget 支持,同時也提供了開發自定義 Widget 的能力。在應用系統中,所有用戶可見部分的顯示和行為都是通過 Widget 來實現的。對于 Widget 的測試就包括對其在瀏覽器中所展現的樣式以及與用戶交互過程中所展現的行為的測試。由于這些與瀏覽器的實現關系非常密切,所以對 Widget 的測試需要針對不同瀏覽器測試其兼容性問題。
  4. UI Look & Feel:Look & Feel 對于 UI 系統也是非常重要的一個方面。在 Dojo 應用系統中,這里主要是對自開發的 CSS 樣式表的驗證,檢查頁面在不同的瀏覽器中所展現出來的樣式是否符合要求。但是,據筆者了解,現有的技術方案都不會對這類問題進行單元測試,而是將這類問題 放到功能測試中進行覆蓋。

DOH 作為一種 JavaScript 單元測試框架,它對以上所描述的幾種類型都有很好的支持。它針對不同類型的單元測試提供了不同的測試模式。

DOH 單元測試運行方式

編寫好的 DOH 單元測試需要怎么運行呢? DOH 支持兩種運行方式:瀏覽器運行方式和命令行運行方式。

瀏覽器運行方式

Dojo 提供了統一界面來運行單元測試,那就是 DOH 中的 runner.html,它把大量單獨的單元測試文件(JavaScript 和 HTML 文件)封裝到一個文件中統一管理。瀏覽器運行方式就是指使用瀏覽器訪問該文件來執行單元測試,并以直觀的方式反映運行結果。開發人員可將自己開發的單元測 試樣例加到 runner.html 中進行測試,運行時可以在瀏覽器的頁面上看到執行進度、打印的 log 信息、UI 顯示界面,以及運行結果等等。

優點 :可以很直觀的查看運行的過程和結果,對 UI 頁面和 Widget 測試天然地提供了執行環境的支持。擅長 UI 頁面及 Widget 的顯示和行為的測試,以及 Ajax 異步通信測試。

不足: 測試過程中需要啟動瀏覽器,UI 的可視化測試需要人為判斷是否符合需求,很難自動化。

命令行運行方式

DOH 還提供了一種不依賴于瀏覽器來運行單元測試的方法,那就是利用命令行的方式。它是通過使用 Dojo util 下面的 JavaScript 執行引擎 – Rhino 來執行單元測試的。運行方式是在 dojo/util/doh 目錄下運行命令:

 java -jar ../shrinksafe/js.jar runner.js testModule= ${yourmodule} 

其中 runner.js 是 DOH 提供的一個執行單元測試的 JavaScript 入口點,${yourmodule} 是自定義的測試模塊名稱。

優點:執行過程不需要人為參與,執行結果也可以通過 DOH 的 assert 自動判斷,擅長 JavaScript 邏輯測試,單元測試很容易編寫。

不足:沒有瀏覽器的環境,無法對可視化頁面或 Widget 的顯示效果進行測試,所使用的 Rhino JavaScript 引擎可能與實際使用的瀏覽器中的 JavaScript 引擎有所差異,這可能會導致一些特定代碼的測試結果與實際運行結果不同,無法進行瀏覽器的兼容性測試。

集成 DOH 單元測試到 Ant build

所謂集成 DOH 單元測試到 Ant build 中,就是尋找一種方式將使用 DOH 編寫的單元測試作為 Ant build 的一個 target 進行運行,并且能夠將單元測試結果體現在 build 的運行結果中,當單元測試失敗時能夠使 build 失敗。

在介紹集成方式之前,首先來看一下 Dojo 項目代碼的組織方式,因為組織方式的不同將會影響到 build 中的一些參數配置。

Dojo 項目代碼的組織方式

以下所介紹的 Dojo 項目代碼的組織方式只是一種比較流行的組織方式,并不是唯一可行的,您可采用不同的組織方式。本文后面實例中的參數配置將以下面介紹的組織方式為準。

通常一個 Web 項目的所有 Web 資源都包含在一個 WebContent 目錄中,其中包括圖片、JavaScript 腳本、CSS 等。Dojo 項目還包括 Dojo 類庫、項目 Dojo 代碼、項目的 Dojo 單元測試代碼。如下圖展示了這些文件的目錄組織方式:


圖 1. Dojo 項目的目錄組織方式
圖 1. Dojo 項目的目錄組織方式

其中,dojo 目錄是 Dojo 的庫目錄;com 是項目 Dojo 代碼目錄,com 是項目代碼包的頂級目錄;tests 是項目單元測試目錄;css 是項目 CSS 文件所在的目錄;images 是項目圖片文件所在的目錄。這些目錄中與本文所介紹的主題相關的有 dojo 目錄和 tests 目錄。

典型的 Dojo 庫的 dojo 目錄包含子文件夾:dojo、dijit、dojox、util,util 中包含 shrinksafe、doh 等目錄;tests 目錄中通常會定義一個 module.js 文件,用于定義所有待運行的 Dojo 單元測試列表。

集成命令行運行方式

DOH 的命令行運行方式通過一個 Java 命令運行 js.jar,并且將 runner.js 和待運行的模塊通過參數傳遞進去。集成這種方式到 Ant build 中是顯而易見的,因為 Ant 直接提供了運行 Java 命令的方式。清單 1 的 Ant 片段就能夠完成運行 Dojo 單元測試的目的(假定 build.xml 文件在 js 目錄中)。


清單 1. 集成命令行運行方式的 Ant 片段
               
 <target name="runUT"> 
    <java jar="dojo/util/shrinksafe/js.jar" fork="true" 
           failonerror="true"> 
        <arg value="dojo/util/doh/runner.js"/> 
        <arg value="dojoUrl=dojo/dojo/dojo.js"/> 
        <arg value="dohBase=dojo/util/doh/"/> 
        <arg value="testUrl=tests/module.js"/> 
        <arg value="testModule=tests.module"/> 
    </java> 
 </target> 

這里的 failonerror 屬性一定要設置為 true,這樣當單元測試失敗時 build 才會失敗。Java 命令里面的 arg 定義了執行 js.jar 時所需要的參數:

  • dojo/util/doh/runner.js: 這個參數指定了 DOH 中 runner.js 相對于 build basedir 的路徑;
  • dojoUrl=dojo/dojo/dojo.js: 這個參數指定了 dojoUrl 的值,即相對于 build basedir,Dojo 庫中 dojo.js 的路徑;
  • dohBase=dojo/util/doh/: 這個參數指定了 dohBase 的值,即相對于 build basedir,DOH 根目錄的相對路徑,即項目目錄中 doh 目錄的路徑;
  • testUrl=tests/module.js: 這個參數指定了 testUrl 的值,即相對于 build basedir,待執行單元測試的路徑,通常直接指向 module.js 文件,它是單元測試的入口;
  • testModule=tests.module: 這個參數指定了 testModule 的值,即待測試模塊的名稱,通常定義為 module.js 所定義的模塊。

當該 target 被執行時,module.js 中 required 的所有單元測試將會被執行,我們寫的每一個單元測試都可以用獨立的 js 文件進行組織,然后通過 required 方式包含在 module.js 中。單元測試的運行結果會被直接打印出來,當有單元測試失敗時 build 就會失敗。清單 2 展示了一個簡單的 DOH 單元測試 sampleTest.js。


清單 2. DOH 單元測試示例
               
 dojo.provide("tests.sampleTest"); 
 doh.register("tests.sampleTest", [ function test_sample() { 
 doh.assertEqual("b", "a"); 
 } ]); 

將這個單元測試添加到 module.js 中,即在 module.js 的頭部位置中添加一行語句:dojo.require("tests.sampleTest ")

清單 3 展示了這個單元測試運行的結果:


清單 3. 失敗的 DOH 單元測試運行結果

run: [java] ------------------------------------------------------------ [java] The Dojo Unit Test Harness, $Rev: 24146 $ [java] Copyright (c) 2011, The Dojo Foundation, All Rights Reserved [java] ------------------------------------------------------------ [java] 1 tests to run in 1 groups [java] ------------------------------------------------------------ [java] GROUP "tests.sampleTest" has 1 test to run [java] _AssertFailure: doh._AssertFailure: assertEqual() failed: [java] expected [java] b [java] but got [java] a [java] : assertEqual() failed: [java] expected [java] b [java] but got [java] a [java] doh._AssertFailure: assertEqual() failed: [java] expected [java] b [java] but got [java] a [java] ERROR IN: [java] (function test_sample() {doh.assertEqual("b", "a");}) [java] ------------------------------------------------------------ [java] | TEST SUMMARY: [java] ------------------------------------------------------------ [java] 1 tests in 1 groups [java] 0 errors [java] 1 failures

BUILD FAILED </pre></td> </tr> </tbody> </table>

如果將 sampleTest.js 中 doh.assertEqual(“b”, “a”) 改為 doh.assertEqual(“a”, “a”),就會是一個執行成功的結果,如清單 4 所示。


清單 4. 成功的 DOH 單元測試運行結果
               
 run: 
     [java] ------------------------------------------------------------ 
     [java] The Dojo Unit Test Harness, $Rev: 24146 $ 
     [java] Copyright (c) 2011, The Dojo Foundation, All Rights Reserved 
     [java] ------------------------------------------------------------ 
     [java] 1 tests to run in 1 groups 
     [java] ------------------------------------------------------------ 
     [java] GROUP "tests.sampleTest" has 1 test to run 
     [java] ------------------------------------------------------------ 
     [java] | TEST SUMMARY: 
     [java] ------------------------------------------------------------ 
     [java]  1 tests in 1 groups 
     [java]  0 errors 
     [java]  0 failures 
 BUILD SUCCESSFUL 

集成瀏覽器運行方式

命令行運行方式在很多方面存在著缺陷,例如:無法測試與 DOM 操作相關的代碼,無法測試瀏覽器的兼容性等等。而瀏覽器運行方式就能夠彌補這些缺陷。可是將瀏覽器運行方式集成到 Ant build 中會相對復雜一些。這種方式需要一個可訪問服務器的支持,這個服務器可以是一個部署好的公用服務器,也可以是在 build 中啟動的一個本地服務器,下面以本地服務器為例。在 build 中啟動一個本地的 Web 服務器可以使用嵌入式的 Web 服務器,例如 Jetty。

在瀏覽器運行方式中,主入口是一個 html 文件,例如 runner.html。我們可以將該文件放在 WebContent 的根目錄中,并將啟動的 Web 服務器的 Web 應用根目錄設置為 WebContent,這樣就可以通過 http://localhost/runner.html 訪問。

所以在 Ant build 中啟動單元測試就變得簡單了,只需要啟動一個瀏覽器并且訪問該地址即可,這里的瀏覽器可以是任何想要測試的瀏覽器,也可以將每種瀏覽器測試一遍。以 Firefox 為例,Ant 片段如清單 5 所示。


清單 5. Ant 中啟動瀏覽器
               
 <target name="runUT"> 
    <exec executable=" C:\Program Files\Mozilla Firefox\firefox.exe"> 
        <arg value="http://localhost/runner.html"/> 
 </exec> 
 </target> 

其中,executable 是瀏覽器應用程序可執行文件或命令的路徑,arg 是訪問 runner.html 的 web 路徑。這條命令可以達到啟動單元測試的目的,但是當瀏覽器被啟動起來以后這條命令就直接返回了,所以 Ant build 可能在單元測試執行結束之前就已經返回了,無法得到單元測試的執行結果。所以我們需要一個辦法讓 Ant build 等待單元測試執行結束。這時候可以借助 Web 服務器來傳遞這個消息。

我們可以實現一個服務器端的消息轉接服務,該服務可以通過 HTTP 寫入或者讀取單元測試的執行結果。在 runner.html 中添加一段 JavaScript 代碼用于在單元測試執行結束后將結果通過 Ajax 方式寫入到服務中。除此之外,我們還需要實現一個 Java 可執行類,該類以一定間隔從服務器端的服務中去取執行結果,當無法取得執行結果時等待一定間隔重試,直到取得執行結果,然后輸出執行結果并且根據執行結果 是否成功決定返回結果。在 Ant build 中,當單元測試被啟動后啟動該 Java 可執行類。通過這個方式我們可以讓 Ant build 等待單元測試執行結束并且將單元測試的結果輸出,當單元測試失敗時讓 build 失敗。

  1. 編寫消息轉接服務

    消息轉接服務是一個簡單的服務器端處理單元,以什么方式實現取決于項目的服務器端技術,可以是 Servlet、REST executor 等等。下面以 REST executor 為例實現該處理單元。

    我們需要將寫入和讀取的 HTTP 請求都定向到這個處理單元中。寫入的 HTTP 請求附帶 HTTP 消息體,讀取的 HTTP 請求沒有 HTTP 消息體。所以該處理單元可以通過是否有 HTTP 消息體來判斷是寫入請求還是讀取請求。實現代碼如清單 6。



    清單 6. 消息轉接服務代碼示例

  2. public class ResultService extends ReqExecutor {

           private String result = ""; 
           public ReqExecutor newInstance() { 
                return this ; 
       } 
            public HTTPResponse execute(HTTPRequest req) { 
                String body = req.getBody(); 
                if(body != null && !body.trim().equals("")) result = body; 
                else { 
                   return new StringResponse(result); 
                } 
            } 
    

    } </pre></td> </tr> </tbody> </table>

    為了保證寫入和讀取的 HTTP 請求都由同一個 ResultService 對象進行處理,所以這里當 newInstance 時就返回 this。

    將該消息注冊到 REST framework 中,并為其指定一個不與其它服務沖突的 URL,例如: ”/resultService”。

    </li>
  3. 修改 DOH 的執行代碼

    在 DOH 中,runner.js 中有一個 doh._report 方法負責在單元測試執行結束后匯報執行結果。我們可以修改該方法,讓它在所有測試執行結束后把運行結果通過 Ajax 方式發送給服務器端的消息轉接服務。修改后的方法體如清單 7。



    清單 7. 修改后的 doh._report 方法
                   
    doh._report = function (){ 
            this .debug("| TEST SUMMARY:"); 
            this .debug(this ._line); 
            this .debug("\t", this ._testCount, "tests in", 
                                 this ._groupCount, "groups"); 
            this .debug("\t", this ._errorCount, "errors"); 
            this .debug("\t", this ._failureCount, "failures"); 
      //--------------------------------------------------------------------------- 
                 // 定義 request 對象,把需要寫入 build 的信息傳遞給 server 端
                 var request = { 
                     //url 為用于接收參數的 rest excutor 
            url: "http://localhost/resultService", 
            handleAs: "text", 
            preventCache: true , 
            content : 
            // 總 test case 數量
            "testCount: " + this ._testCount + "\n" + 
            // 總 test suit 數量
            "groupCount: " + this ._groupCount + "\n" + 
            // 出錯數量
            "errorCount: " + this ._errorCount + "\n" + 
            // 失敗數量
            "failureCount: " + this ._failureCount + "\n" + 
            // 全部執行過程中的 log 內容
            "logBody: " + dojo.byId("logBody").innerHTML + 
                     // 執行結果是成功或者失敗,將被 Ant build 用于決定是否讓 build 失敗
            this ._errorCount + this ._failureCount > 0 ? "successful"  : "failed", 
            load: function (data, ioargs){ 
                console.log("ok"); 
            }, 
            error: function (error){ 
                console.log("error"); 
            } 
                   }; 
                   // 向 server 端發送 POST 請求
                   dojo.xhrPost(request); 
     } 

    方法體中分割線以上部分為原方法體,以下部分為增加的代碼。在該方法中可以分別通過內部變量或者 innerHTML 獲得所有已執行單元測試的統計信息(比如:test case 的數量、test suit 的數量)和執行結果(比如:出錯數量、失敗數量、執行的 log 內容)。在以上的樣例實現中,只是簡單的將所有這些內容以純文字的形式返回給消息轉接服務。如果不希望這樣,當然也可以選擇其它的方式(例如 JSON)和內容,但是這樣也同時需要修改消息轉接服務和 Ant build 中的 Java 可執行類。

  4. 集成 Java 可執行類到 Ant build

    這個 Java 可執行類需要提交 HTTP 請求到 http://localhost/resultService,并且獲取消息轉接服務所保存的執行結果。代碼如清單 8。



    清單 8. Java 可執行類示例

    import java.io.IOException; import java.io.InputStream; import java.net.URL;

    public class ResultRequest {

    public static void main(String[] args) throws IOException { URL u = new URL("http://localhost/resultService&quot;); //int tryCounts = 0; while (true ){ //if(tryCounts > 60) System.exit(1); //tryCounts++; InputStream inStream = u.openStream(); String result = readContentFromStream(inStream); if (result != null && !result.trim().equals("")) { System.out.println(result); if (result.endsWith("failed")) System.exit(1); else return ; } else { Thread.sleep(1000); } } } } </pre></td> </tr> </tbody> </table>

    該類打開連接消息轉接服務的 URL,獲得 URL 所返回的內容,如果返回的內容為空則繼續等待,等待時間可以設置為一個合適的時間,比如 1 秒鐘;如果返回的內容不為空則說明已經取到單元測試的運行結果,然后輸出取到的結果并且從結果中獲得單元測試是否成功,如果不成功則以錯誤代碼返回,否則 直接返回。

    將該類集成到 Ant build 中,首先將該類編譯打包,然后放在一個 build 腳本可以訪問的庫目錄中,例如 WebContent/lib。Ant 片段如清單 9。



    清單 9. 集成 Java 可執行類到 Ant
                   
     <target name="testResult" depends="runUT"> 
        <java classname="ResultRequest" classpath="lib" failonerror=”true”/> 
     </target> 

    該 target 在 runUT target 后執行,所包含的工作就是執行這個 Java 類。

    </li> </ol>

    回頁首

    提高代碼可測試性

    根據前面的介紹,DOH 支持兩種運行模式,這兩種運行模式也可以通過不同的方式集成到 Ant build 中。但是,由于這兩種方式都有各自的特點以及所擅長的測試類型,所以我們可以通過重構代碼和編寫恰當的單元測試來提高代碼的可測試性。

    首先,盡可能地將代碼中的復雜邏輯抽象為獨立的 JavaScript 邏輯模塊,以便于可以按照 JavaScript 代碼邏輯進行測試,并且使用命令行的方式進行運行。例如,Widget 中不與 DOM 內容進行交互的方法邏輯;Ajax 通信中對請求或者響應消息進行處理的邏輯代碼等等。

    其次,將無法抽象成為獨立 JavaScript 邏輯模塊的代碼的測試選擇瀏覽器方式運行。例如,Widget 的行為中需要與 DOM 內容進行交互的方法;在各種瀏覽器中行為可能會不一樣的代碼等等。

    通過將單元測試集成到 Ant build 中,您也可以通過自己的嘗試和摸索不斷發現其它能夠提高代碼可測試性的方法。

    小結

    本文首先簡單介紹了 Dojo 單元測試的幾種類型以及使用 DOH 進行 Dojo 單元測試的兩種運行方式 ,然后重點闡述了這兩種運行方式如何與 Ant build 進行集成以滿足敏捷開發的需要。將單元測試集成到 build 是敏捷開發中持續集成的關鍵要素,通過使用本文所介紹的方法將 Dojo 單元測試集成到項目的 build 中,可以使項目開發成員在開發過程中及時地發現 Dojo 代碼中的缺陷,從而提高項目的開發效率和代碼質量。

    文章出處: IBM developerWorks

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