• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
    • 軟件測試技術
    • 軟件測試博客
    • 軟件測試視頻
    • 開源軟件測試技術
    • 軟件測試論壇
    • 軟件測試沙龍
    • 軟件測試資料下載
    • 軟件測試雜志
    • 軟件測試人才招聘
      暫時沒有公告

    字號: | 推薦給好友 上一篇 | 下一篇

    追求代碼質量: 對 Ajax 應用程序進行單元測試

    發布: 2007-11-19 11:14 | 作者: Andrew Glover | 來源: IBM | 查看: 73次 | 進入軟件測試論壇討論

    領測軟件測試網
    您可能從編寫 Ajax 應用程序中獲得了極大樂趣,但是對它們執行單元測試卻著實讓人頭痛。 在本文中,Andrew Glover 著手解決 Ajax 的弱點(其中之一),即應對異步 Web 應用程序執行單元測試的固有挑戰。幸運的是,他發現在 Google Web Toolkit 的幫助下,解決這個特殊的代碼質量問題要比預想的容易。

    Ajax 在近期無疑是 Web 開發界最時髦的字眼之一 —— 與 Ajax 相關的工具、框架、書籍以及 Web 站點的劇增就是該技術流行的最好證明。此外,Ajax 應用程序也相當靈巧,不是嗎?不過,像任何一個開發過 Ajax 應用程序的人證實的一樣,對 Ajax 執行測試真的很不方便。事實上,Ajax 的出現已經從根本上使得許多測試框架和工具失效,因為它們并沒有針對異步 Web 應用程序測試進行設計!

    有趣的是,某個支持 Ajax 的框架的開發人員注意到了這個限制,并為此做了一些非常新穎的設計:內置的可測試性。除此之外,由于該框架簡化了使用 Java™ 代碼(而不是 JavaScript)創建 Ajax 應用程序,它的起點甚高,并且充分利用了 Java 平臺上無可置疑的標準測試框架:JUnit。

    我所論及的框架當然是非常流行的 Google Web Toolkit,也就是 GWT。在本文中,我將向您展示 GWT 如何實際地利用 Java 兼容性,使 Ajax 應用程序的每個部分都能像與之對應的同步應用程序一樣進行測試。

    改進代碼質量
    別錯過 Andrew Glover 的 代碼質量討論論壇,里面有關于代碼語法、測試框架以及如何編寫專注于質量的代碼的幫助。

    JUnit 和 GWTTestCase

    因為與 GWT 有關的 Ajax 應用程序采用 Java 代碼編寫,所以非常適合開發人員使用 JUnit 進行測試。事實上,GWT 開發小組還為此創建了一個幫助器類 GWTTestCase,擴展自 JUnit 的 3.8.1 TestCase。該基類添加了一些功能,可測試 GWT 代碼并處理某些基礎實現從而啟動并運行 GWT 組件。

    Google Web Toolkit
    Google Web Toolkit 在 Java Web 開發社區的發布聲勢浩大,同時也獲得了與之相稱的巨大轟動。GWT 為利用 Java 代碼進行設計、構建和部署支持 Ajax 的 Web 應用程序提供了一種新穎的方式。Java Web 開發人員不再需要學習 JavaScript 并花費數個小時解決特定于瀏覽器的問題,他們可以直接進行與 Ajax 有關的富含信息的動態 Web 應用程序設計。

    需要提醒的是:GWTTestCase 并非用來測試與 UI 相關的代碼 —— 它是為了便于測試那些由 UI 交互觸發 的異步問題。對 GWTTestCase 用途的誤解使許多剛接觸 GWT 的開發人員備受挫折,因為他們期望能夠用它方便地模擬用戶界面,但最終發現這是徒勞的。

    Ajax 組件有兩個基本組成:體驗和功能,這些都被設計成異步方式。圖 1 演示了一個模擬 Web 表單的簡單 Ajax 組件。由于該組件支持 Ajax,表單的提交是異步執行的(即:無需重新載入與傳統表單提交關聯的頁面)。


    圖 1. 一個支持 Ajax 的簡單 Web 表單
    一個支持 Ajax 的簡單 Web 表單。

    輸入一個有效單詞,單擊組件的 Submit 按鈕,將向服務器發送消息請求該單詞的定義。該定義通過回調異步返回,相應地插入到 Web 頁面,如圖 2 所示:


    圖 2. 單擊 Submit 按鈕后顯示響應
    單擊 Submit 按鈕后顯示響應。

    功能性和集成測試

    圖 2 所示的交互測試可用于多個不同場景,但是其中兩種場景最為常見。從功能性觀點考慮,您或許希望編寫一個測試:填入表單值,單擊 Submit 按鈕,然后驗證表單是否顯示定義。另外一個選擇是集成測試,使您能夠驗證客戶端代碼的異步功能。GWT 的 GWTTestCase 正是被設計用來執行此類測試。

    需要牢記的是:在 GWTTestCase 測試用例環境下不可以進行用戶界面測試。在設計和構建 GWT 應用程序時,您必須清楚不要依賴用戶界面 測試代碼。這種思路需要把交互代碼從業務邏輯中分離出來,正如您已經了解的,這是最佳的入門實踐!

    舉例而言,重新查看圖 1 和圖 2 所示的 Ajax 應用程序。該應用程序由四個邏輯部分構成:TextBox 用于輸入目標單詞,Button 用于執行單擊,還有兩個 Label(一個用于 TextBox,另一個顯示定義)。實際 GWT 模塊的初始方法如清單 1 所示,但是您該如何測試這段代碼呢?


    清單 1. 一個有效的 GWT 應用程序,但是如何測試它?
                    public class DefaultModule implements EntryPoint { public void onModuleLoad() {   Button button = new Button("Submit");   TextBox box = new TextBox();   Label output = new Label();   Label label = new Label("Word: ");   HorizontalPanel inputPanel = new HorizontalPanel();   inputPanel.setStyleName("input-panel");   inputPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);   inputPanel.add(label);   inputPanel.add(box);   button.addClickListener(new ClickListener() {    public void onclick(Widget sender) {      String word = box.getText();      WordServiceAsync instance = WordService.Util.getInstance();       try {         instance.getDefinition(word, new AsyncCallback() {                      public void onFailure(Throwable error) {             Window.alert("Error occurred:" + error.toString());           }           public void onSuccess(Object retValue) {             output.setText(retValue.toString());           }		  });        }catch(Exception e) {          e.printStackTrace();		}	}   });   inputPanel.add(button);   inputPanel.setCellVerticalAlignment(button,     HasVerticalAlignment.ALIGN_BOTTOM);   RootPanel.get("slot1").add(inputPanel);   RootPanel.get("slot2").add(output);   }}

    清單 1 的代碼在運行時發生了嚴重的錯誤:它無法按照 JUnit 和 GWT 的 GWTTestCase 進行測試。事實上,如果我試著為這段代碼編寫測試,從技術方面來說它可以運行,但是無法按照邏輯工作?紤]一下:您如何對這段代碼進行驗證?惟一可用于測試的 public 方法返回的是 void, 那么,您怎么能夠驗證其功能的正確性呢?

    如果我想以白盒方式驗證這段代碼,就必須分離業務邏輯和特定于用戶界面的代碼,這就需要進行重構。這本質上意味著把清單 1 中的代碼分離到一個便于測試的獨立方法中。但是這并非聽上去那么簡單。很明顯組件掛鉤是通過 onModuleLoad() 方法實現,但是如果我想強制其行為,可能 必須操縱某些用戶界面(UI)組件。





    回頁首


    分解業務邏輯和 UI 代碼

    第一步是為每個 UI 組件創建訪問器方法,如清單 2 所示。按照該方式,我可以在需要時獲取它們。


    清單 2. 向 UI 組件添加訪問器方法使其可用
                    public class WordModule implements EntryPoint {  private Label label;  private Button button;  private TextBox textBox;  private Label outputLabel;    protected Button getButton() {   if (this.button == null) {    this.button = new Button("Submit");   }   return this.button;  }  protected Label getLabel() {   if (this.label == null) {    this.label = new Label("Word: ");   }   return this.label;  }  protected Label getOutputLabel() {   if (this.outputLabel == null) {    this.outputLabel = new Label();   }   return this.outputLabel;  }  protected TextBox getTextBox() {   if (this.textBox == null) {    this.textBox = new TextBox();    this.textBox.setVisibleLength(20);   }   return this.textBox;  }}

    現在我實現了對所有與 UI 相關的組件的編程式訪問(假設所有需要進行訪問的類都在同一個包內)。以后我可能需要使用其中一種訪問進行驗證。我現在希望限制 使用訪問器,如我已經指出的,這是因為 GWT 并非設計用來進行交互測試。所以,我不是真的要試圖測試某個按鈕實例是否被單擊,而是要測試 GWT 模塊是否會對給定的單詞調用服務器端代碼,并且服務器端會返回一個有效定義。方法為將 onModuleLoad() 方法的定義獲取邏輯推入(不是故意用雙關語。┮粋可測試方法中,如清單 3 所示:


    清單 3. 重構的 onModuleLoad 方法委托給更易于測試的方法
                    public void onModuleLoad() {  HorizontalPanel inputPanel = new HorizontalPanel();  inputPanel.setStyleName("disco-input-panel");  inputPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);  Label lbl = this.getLabel();  inputPanel.add(lbl);  TextBox txBox = this.getTextBox();  inputPanel.add(txBox);  Button btn = this.getButton();  btn.addClickListener(new ClickListener() {   public void onClick(Widget sender) {     submitWord();   }  });  inputPanel.add(btn);  inputPanel.setCellVerticalAlignment(btn,      HasVerticalAlignment.ALIGN_BOTTOM);  if(RootPanel.get("input-container") != null) {   RootPanel.get("input-container").add(inputPanel);  }  Label output = this.getOutputLabel();  if(RootPanel.get("output-container") != null) {   RootPanel.get("output-container").add(output);  }}

    如清單 3 所示,我已經把 onModuleLoad() 的定義獲取邏輯委托給 submitWord 方法,如清單 4 定義:


    清單 4. 我的 Ajax 應用程序的實質!
                    protected void submitWord() {  String word = this.getTextBox().getText().trim();  this.getDefinition(word);}protected void getDefinition(String word) { WordServiceAsync instance = WordService.Util.getInstance(); try {   instance.getDefinition(word, new AsyncCallback() {   public void onFailure(Throwable error) {    Window.alert("Error occurred:" + error.toString());   }   public void onSuccess(Object retValue) {    getOutputLabel().setText(retValue.toString());   }  }); }catch(Exception e) {   e.printStackTrace(); }}

    submitWord() 方法又委托給 getDefinition() 方法,我可以用 JUnit 測試它。getDefinition() 方法從邏輯上獨立于特定于 UI 的代碼(對于絕大部分而言),并且可以在沒有單擊按鈕的情況下得到調用。另一方面,與異步應用程序有關的狀態問題和 Java 語言的語義規則也規定了我不能在測試中完全 避免與 UI 相關的交互。仔細查看清單 4 中的代碼,您能夠發現激活異步回調的 getDefinition() 方法操縱了某些 UI 組件 —— 一個錯誤警告窗口以及一個 Label 實例。

    我還可以通過獲得輸出 Label 實例的句柄,斷言其文本是否是給定單詞的定義,從而驗證應用程序的功能。在用 GWTTestCase 測試時,最好不要 嘗試手工強制改變組件狀態,而應該讓 GWT 完成這些工作。舉例而言,在清單 4 中,我想驗證對某個給定單詞返回了其正確定義并放入一個輸出 Label 中。無需操作 UI 組件來設置這個單詞;我只要直接調用 getDefinition 方法,然后斷言 Label 具有對應定義。

    既然我已經編寫好了計劃進行測試的 GWT 應用程序,我需要實際編寫測試,這意味著設置 GWT 的 GWTTestCase。





    回頁首


    設置 GWTTestCase

    若想從 GWTTestCase 的測試魔力中獲益,需要遵守一些規則。幸運的是,規則很簡單:

    • 所有用于實現測試的類和待測 GWT 模塊必須位于同一個包內。

    • 運行測試時,您必須至少傳遞一個 VM 參數,指明在哪種 GWT 模式(托管或 Web)下運行測試。

    • 您必須實現 getModuleName() 方法,它返回一個 String,表示您的 XML 模塊文件。
    最后,因為與服務器端實體通信的 Ajax 應用程序在本質上是異步的,GWT 還提供了 Timer 類,以便延遲 JUnit,使異步行為在進行相關斷言之前全部完成。

    CC>實現 getModuleName 和 Timer 類

    我已經指出,我的測試集中于 getDefinition() 方法(如 清單 4 所示)。您可以從代碼看到,測試邏輯非常簡單:傳入一個單詞(比如 pugnacious),然后驗證相應的 Label 文本是否得到正確定義。很簡單,對嗎?但是不要忘記,getDefinition() 方法在 AsyncCallback 對象中具有某種相關的異步性。

    GWTTestCase 類是一個抽象 類,因為它的 getModuleName() 方法就是這么聲明的;因此,當您擴展該類時,您需要實現 getModuleName()(除非您是在為框架創建自己的基抽象類)。模塊名實際上就是您的 GWT XML 文件所在的包結構的名稱去掉文件擴展名。舉個例子,在本例中,我有一個名為 WordModule.gwt.xml 的 XML 文件,它位于一個目錄結構如:com/acme/gwt。相應的,模塊的邏輯名稱為 com.acme.gwt.WordModule,這會讓您想到 Java 平臺的普通包模式。

    我已經得到一個模塊名,可以開始定義測試用例了,如清單 5 所示:


    清單 5. 您必須實現 getModuleName 方法并提供一個有效的名字
                    import com.google.gwt.junit.client.GWTTestCase;import com.google.gwt.user.client.Timer;public class WordModuleTest extends GWTTestCase { public String getModuleName() {   return "com.acme.gwt.WordModule"; }}

    到目前為止一切良好,但是我還沒有執行任何測試!由于我的 Ajax 應用程序使用 AsyncCallback 對象,在通過測試用例調用 getDefinition() 方法時, 我必須強迫 JUnit 延遲運行;否則測試將由于沒有任何響應而失敗。這就要用到 GWT 的 Timer 類。Timer 使我能夠重寫 getDefinition()run 方法,在 Timer 內完成測試用例邏輯。(測試用例以獨立線程運行,有效地阻塞 JUnit 完成整個測試用例)。

    以我的測試為例,我將首先調用 getDefinition() 方法,然后提供一個 Timerrun() 方法的實現。run() 方法得到輸出 Label 實例的文本并驗證是否是正確定義。定義了 Timer 實例后,我就需要確定其何時運行,同時強制 JUnit 掛起直至 Timer 實例完成。也許聽起來有點復雜,不必擔心,因為實踐起來非常簡易。實際上,清單 6 展示了整個過程:


    清單 6. 使用 GWT 輕松測試
                    public void testDefinitionValue() throws Exception { WordModule module = new WordModule(); module.getDefinition("pugnacious"); Timer timer = new Timer() {   public void run() {    String value = module.getOutputLabel().getText();	String control = "inclined to quarrel or fight readily;...";   	assertEquals("should be " + control, control, value);    finishTest();   }  };  timer.schedule(200);  delayTestFinish(500);}

    正如您所見,Timerrun() 方法是我真正驗證 Ajax 應用程序功能及其應用遠程過程調用的地方。請注意 run 方法的最后一步是調用 finishTest() 方法,它意味著一切如預期運行,JUnit 可以不受阻塞正常運行。在實踐中,您可能會發現需要根據異步行為完成所需的時間調整延遲時間。但用 JUnit 測試 GWT 應用程序的要點在于:您能夠在無需 部署完整功能的 Web 應用程序的情況下測試它。因此,您能夠更早地 并且更 頻繁地 測試您的 GWT 應用程序。





    回頁首


    運行 GWT 測試

    使用 GWT 進行功能測試
    像本文演示的這類簡單 Ajax 應用程序可以 從功能角度進行驗證,使用包括 Selenium 在內的框架,它會驅動瀏覽器模擬實際用戶行為。不過,要想用 Selenium 運行功能測試,您必須部署完整功能的 Web 應用程序。

    前面我曾提到,如果您想實際運行您的 GWT JUnit 測試,您必須執行大量瑣碎的工作來配置運行環境。比如說,要想通過 Ant 的 junit 任務運行我的測試,我就必須確保某些文件位于類路徑中并向低層 JVM 提供一個參數。特別是,在調用 junit 任務時,我還要確保托管源文件(以及測試)的目錄(或多個目錄)位于類路徑中,還要告訴 GWT 以何種模式運行。我傾向于使用 hosted 模式,這意味著要使用 www-test 標志,如清單 7 所示:


    清單 7. 用 Ant 運行 GWT 測試
                    <junit dir="./" failureproperty="test.failure" printSummary="yes" 	 fork="true" haltonerror="true">	 <jvmarg value="-Dgwt.args=-out www-test" />  <sysproperty key="basedir" value="." /> <formatter type="xml" /> <formatter usefile="false" type="plain" /> <classpath>  <path refid="gwt-classpath" />  <pathelement path="build/classes" />  <pathelement path="src" />  <pathelement path="test" /> </classpath> <batchtest todir="${testreportdir}">  <fileset dir="test">   <include name="**/**Test.java" />  </fileset> </batchtest></junit>

    運行 GWT 測試現在轉變成調用問題了。還需注意的是 GWT 測試屬于輕量級測試, 所以我可以頻繁運行測試,甚至是連續運行,就像我在一個持續集成環境(Continuous Integration)中一樣。





    回頁首


    結束語

    在本文所示的 GWT 測試用例中,您已經看到用于驗證 Ajax 應用程序所需的基本步驟。您可以繼續測試我的示例 GWT 應用程序,比如測試一些邊界用例,但是我認為重點在于:如果使用包含測試特性的框架編寫 Ajax 應用程序,測試要比想象中容易。

    要對 GWT 應用程序進行良好測試(對絕大多數應用程序也適用),關鍵在于設計應用程序時要把測試一并考慮。還要注意 GWTTestCase 不是被用來進行交互測試的。您不能使用 GWTTestCase 直接模擬用戶。不過您能夠以一種間接的方式用它來驗證用戶交互,正如本文中演示的那樣。

    延伸閱讀

    文章來源于領測軟件測試網 http://www.kjueaiud.com/

    TAG: ajax 單元測試


    關于領測軟件測試網 | 領測軟件測試網合作伙伴 | 廣告服務 | 投稿指南 | 聯系我們 | 網站地圖 | 友情鏈接
    版權所有(C) 2003-2010 TestAge(領測軟件測試網)|領測國際科技(北京)有限公司|軟件測試工程師培訓網 All Rights Reserved
    北京市海淀區中關村南大街9號北京理工科技大廈1402室 京ICP備2023014753號-2
    技術支持和業務聯系:info@testage.com.cn 電話:010-51297073

    軟件測試 | 領測國際ISTQBISTQB官網TMMiTMMi認證國際軟件測試工程師認證領測軟件測試網

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>