Swing 是一個強大的 GUI 工具包;它可擴展、可配置且跨平台。不過 Swing 的靈活性既是它的主要優勢也是它的重大弱點。Swing 可以不同的方式構建同一 UI。例如,您可以使用插頁、空白邊框或填充符在 GUI 組件之間置入間隔。鑒 於 Swing 選項太多,了解現有 GUI 如同編寫新 GUI 一樣令人畏懼,且將其視 覺外觀與底層代碼對應起來也並非易事。(試著在閱讀幾個使用 GridBagLayout 的代碼行時想象一下 GUI。)
不管您是在維護未曾寫過的 Swing GUI 還是集成第三方 GUI 組件到您的應 用程序中,理解代碼的一種合理方法是編寫測試。在編寫測試的同時您也就熟悉 了未知代碼的內部構造。這樣做會同時產生另一個有價值的結果,即您最終會有 一個測試套件,它有助於在維護代碼時預防回歸的引入。對於第三方 GUI 組件 ,測試套件有助於查明新版本的庫是否引入了任何行為變化。
一開始最好先編寫功能測試,以了解 GUI 如何響應用戶輸入。為 GUI 編寫 測試比為非可視化代碼編寫測試更復雜,因為:
理論上,測試必須是自動化的,但是 GUI 則是供人類 — 而非計算機程序 — 使用的。
傳統的單元測試涉及到隔離類的測試,不適合 GUI 組件。在 GUI 術語中, 一個 “單元” 涉及多個 GUI 組件的協作,因此它本身包含不止一個類。
GUI 響應用戶生成的事件。要測試 GUI,你需要一種可以模擬用戶輸入的方 法,一直等到生成的事件散播給所有偵聽者,然後檢查結果,就像 GUI 響應用 戶一樣。編寫模擬用戶與 GUI 交互的代碼會很繁瑣且易出錯。
更改 GUI 的布局不應影響強健的功能測試。
另外一個問題就是您必須事先熟知要測試的 GUI 的結構和行為,否則您不知 道自動化測試應使用哪些組件,且哪些內容需要驗證。總而言之,要編寫 GUI 測試,您必須知道:
GUI 中用於測試的組件
如何在測試中惟一標識這樣的組件
特定用例中組件的預期狀態(或屬性)
使用可視化設計工具(比如 NetBeans Matisse)您可以弄清 GUI 的結構。 不過這種工具僅顯示 GUI 的設計時信息,這會與您在運行時看到的不一樣。例 如,有些組件可能會根據用戶輸入而顯示為可見或不可見。
傳統的調試程序在執行特定用例時不能幫助您了解 GUI 的狀態。當調試程序 停在 Swing 代碼中置入的斷點時,GUI 繪圖中斷,使得 GUI 看起來像一個空白 方框。理想情況下,當 您使用調試程序進行單步調試時您希望看到 GUI 運作的 方式。
幸運的是,兩個開源工具 — Swing Explorer 和 FEST-Swing — 可以幫助 您快速了解現有 Swing 代碼。本文向您介紹這些工具,向您展示如何結合使用 它們檢查應用程序的 GUI 結構,測試其功能,並識別潛在問題。
要探究的應用程序
對於文章的大部分示例,我將使用一種名為 HTMLDocumentEditor 的免費功 能性 HTML 編輯器,將其作為要測試的應用程序。如果您想自己完成示例,可以 下載 應用程序和樣例測試代碼。圖 1 顯示了運行中的 HTMLDocumentEditor:
圖 1. HTML 編輯器
在編寫 GUI 測試之前,您需要了解 GUI 的構成方式。HTML 編輯器很簡單, 包含一個文本區域和若干用於打開、保存和編輯 HTML 文檔的菜單。
熟悉每個組件的具體類型也是很重要的。這將有助於您了解 GUI 組件通過 API 為您提供哪些動作或屬性用於測試。對於 HTML 編輯器,您需要確定文本區 域是否是 JTextArea、JTextPane 或一個通用的 GUI 組件。確定 GUI 組件類型 的一種方法是檢查源碼。根據 GUI 的實現方式,這可以是個簡單工作,也可以 是挑戰性任務。HTMLDocumentEditor 的源碼可讀且易於掌握,快速檢查該源碼 後發現文本區域是一個 JTextPane。但在您的技術生涯中,你很可能會遇到寫得 很差的 GUI 代碼,非常難以理解。當這種情況發生時,您可以選擇花費大量時 間破譯代碼,也可以選擇尋找一種可提供有效幫助的工具。
Swing Explorer 簡介
Swing Explorer 允許您可視地檢查 Swing GUIs 的內部結構。其簡單直觀的 UI 使我們更易於:發現 GUI 中的所有組件,調查其繪制方式,檢查任意時間的 屬性,等等。
Swing Explorer 可同時作為獨立應用程序和插件在 Eclipse 和 NetBeans 中使用。建議通過 IDE 插件使用它。在本文中,我將使用 Eclipse 插件。
安裝插件之後,我使用 Swing Explorer 啟動了 HTML 編輯器主類,如圖 2 所示:
圖 2. Swing Explorer 中啟動的編輯器應用程序
Swing Explorer 提供多個視圖幫助您查明 Swing GUI 的內部構造:
顯示組件層次結構的一個樹視圖
檢查中的 GUI
一個選項卡面板,顯示選中組件(名稱、大小等)的屬性,且包含其他有用 、有趣的工具
使用 Swing Explorer 了解 GUI 的構造很簡單。出於本練習的需要,假定您 不能通過閱讀源碼查明在 HTML 編輯器中用作文本區域的組件的類型。通過 Swing Explorer,您僅需選擇組件樹視圖上的文本區域或單擊 GUI 中顯示的組 件本身。在下面的圖 3 中,Swing Explorer 確認文本區域是一個 JTextPane:
圖 3. 顯示選中組件屬性的 Swing Explorer
了解和測試應用程序行為
一旦確定要測試的 GUI 的結構,下一步就要了解應用程序的行為,這樣才能 知道要驗證的期望值是哪些。這可以通過不同的方式完成:會見當前終端用戶, 閱讀應用程序文檔(如果有的話)或僅僅使用應用程序本身。
一開始我要選擇兩個用例進行測試:
打開一個 HTML 文件
改變文檔字體的顏色
現在我准備開始編寫功能 GUI 測試了。
功能 GUI 測試驗證應用程序是否按預期運行。它專注於應用程序的行為,而 非 GUI 的外觀。以下因素是創建強健的功能 GUI 測試所必不可少的:
能夠模擬用戶輸入(鍵盤和鼠標)
擁有用於查找 GUI 組件的可靠機制
能夠容許組件位置或布局的變化
空想:直接使用 Robot
要確保一個自動化測試能真正模擬用戶輸入,您需要生成操作系統級的 “原 生” 事件,就像用戶在使用鍵盤和鼠標一樣。JDK 自 1.3 版本以來通過 Abstract Window Toolkit (AWT) Robot 為輸入模擬提供支持。不過 Robot 僅 對屏幕坐標有效,而對 Swing 組件參考無效,因此直接使用它會使測試很脆弱 ,這意味著任何布局變化都會中止測試。
而且 AWT Robot 級別太低;它只知道如何單擊鼠標按鈕和按鍵。您需要編寫 能翻譯高級動作的代碼,比如選擇該組合框中的第三個元素 放入 Robot 動作中 。根據測試所需的動作數量和相關組件的不同類型,這需要大量工作。另外, AWT Robot 不為組件查詢(比如查找帶有文本 “OK” 的按鈕)提供可靠機制。 您還是需要自己編寫代碼。
總而言之,直接使用 AWT Robot 需要大量精力和時間。當編寫功能 GUI 測 試時,您需要將注意力放在要查證的行為上,而不是放在使 GUI 測試成為可能 的底層管道上。
FEST-Swing 簡介
FEST(Fixtures for Easy Software Testing)Swing 模塊是能夠輕松創建 和維護強健的功能 GUI 測試的一個庫。它的主要特性包括:
建立於 AWT Robot 之上,用於模擬真實的用戶輸入。
有一個緊湊、直觀、可讀的連貫接口,能簡化功能 GUI 測試的創建和維護。 清單 1 顯示了如何編碼實現高級動作:在 firstName 文本字段中輸入 “luke ” 文本然後單擊 “ok” 按鈕。
清單 1. FEST-Swing 的連貫接口
dialog.textBox("firstName").enterText("Luke");
dialog.button("ok").click();
驗證 GUI 組件狀態的斷言方法。清單 2 顯示了一個斷言,它驗證了標簽名 為 “answer” 的文本是 “21”:
清單 2. FEST-Swing 的斷言
dialog.label("answer").requireText("21");
促進強健測試:布局變化不會 中斷測試。
支持出現在 JDK 中的 Swing 組件。
支持 JUnit 4 和 TestNG。
為正確的 Swing 線程使用提供驗證。
簡化故障檢修失敗測試。
用 FEST-Swing 編寫功能 GUI 測試
目前我們已經了解了編輯器應用程序的 GUI 的結構,收集了要測試的用例, 找到了可靠的測試工具,終於可以開始編寫功能 GUI 測試了。
用例:打開一個 HTML 文件
在 HTML 編輯器中打開文件需要執行以下操作:
選擇 File > Open 子菜單
在顯示的文件選擇器中選擇要打開的文件
確保編輯器加載了文件內容
清單 3 顯示了這一用例的代碼:
清單 3. 打開 HTML 文件的測試
public class HTMLDocumentEditor_Test extends FestSwingJUnitTestCase {
private FrameFixture editor;
protected void onSetUp() {
editor = new FrameFixture(robot(), createNewEditor ());
editor.show();
}
@RunsInEDT
private static HTMLDocumentEditor createNewEditor() {
return execute(new GuiQuery<HTMLDocumentEditor>() {
protected HTMLDocumentEditor executeInEDT() {
return new HTMLDocumentEditor();
}
});
}
@Test
public void should_open_file() {
editor.menuItemWithPath("File", "Open").click();
JFileChooserFixture fileChooser = findFileChooser ().using(robot());
fileChooser.setCurrentDirectory(temporaryFolder())
.selectFile(new File("helloworld.html"))
.approve();
assertThat(editor.textBox("document").text()).contains ("Hello");
}
}
以下內容詳細介紹了清單 3 中的測試工作:
第一行擴展了 FEST-Swing 的 FestSwingJUnitTestCase。它提供對 FEST- Swing Robot 的自動創建,對正確 Swing 線程的驗證(稍後詳細介紹),對資 源的自動清理(關閉打開的窗口,釋放鼠標和鍵盤,等等)。
editor = new FrameFixture(robot(), createNewEditor()); 創建一個新的 FrameFixture,能夠在 Frame 上模擬用戶輸入,查詢它內部的組件(使用多種 搜索標准)並驗證其狀態。
editor.show();在屏幕上顯示 HTML 編輯器。
@RunsInEDT 用文檔記錄保證要在事件調度線程(EDT)中執行的 createNewEditor() 方法。
return execute(new GuiQuery<HTMLDocumentEditor>() 創建 EDT 中 HTMLDocumentEditor 的一個新實例。
在 editor.menuItemWithPath("File", "Open").click(); 中,FEST-Swing 模擬一個用戶單擊File > Open 子菜單。
在 JFileChooserFixture fileChooser = findFileChooser().using(robot ()); 中,FEST-Swing 查找由 HTML 編輯器啟動的 “Open File” JFileChooser。
在接下來三行中,FEST-Swing 模擬用戶選擇位於系統臨時文件夾中的 helloworld.html 文件。
assertThat(editor.textBox("document").text()).contains("Hello"); 通 過檢查文件中是否包含 Hello 來驗證是否將文件加載到了編輯器中。
注意,清單 3 按照名稱(editor)查詢 JTextPane。這是在一個測試中查找 組件最可靠的方式;它保證組件查找從不失敗,即使 GUI 的布局在將來會改變 。
用例:改變文檔字體的顏色
要驗證 HTML 編輯器將文檔字體的顏色改為黃色,您需要:
選擇 Color > Yellow 子菜單
在編輯器中輸入內容
驗證輸入文本的顏色是黃色
清單 4 顯示了如何使用 FEST-Swing 實現上述操作:
清單 4. 用於更改文檔字體顏色的測試
@Test
public void should_change_document_color() {
editor.menuItemWithPath("Color", "Yellow").click();
JTextComponentFixture textBox = editor.textBox();
textBox.enterText("Hello");
assertThat(textBox.text()).contains("<font color=\"#ffff00 \">Hello</font>");
}
到目前為止,我展示了如何測試簡單的 GUI 組件,比如菜單和文本框。接下 來我將介紹一種不太直觀的測試模式。
更加復雜的測試
為展示 FEST-Swing 直觀緊湊的 API,我將使用 Swing 的一個高度復雜的組 件 — JTable。
我將使用 Sun 公司 Swing 教程中的 TableDialogEditoDemo 應用程序。該 應用程序使用帶有定制編輯器的 JTable:JComboBoxes 和 JCheckBoxes,如圖 4 所示:
圖 4. TableDialogEditDemo
為用作示例,我將編寫一個測試,模擬用戶選擇 0 行處組合框中的第二個元 素。測試要執行的動作是:
按需上下滾動表格使該行可見。
單擊第 0 行第 2 列的單元格。
等待組合框出現。
找到並單擊組合框。
從組合框中選擇第二個元素。
這只是對我要編碼的動作的粗略描述。編寫真實代碼並非微不足道的工作。 幸運的是,FEST-Swing 的 API 簡化了該任務,如清單 5 所示:
清單 5. 選擇 0 行處組合框中的第三個元素
dialog.table.enterValue(row(0).column(2), "Knitting");
FEST-Swing 可以簡化 GUI 測試甚至是復雜測試的編寫和閱讀。
Swing 線程
Swing 是一個單線程 UI 工具包。因為它不是線程安全的,所以所有 Swing 代碼必須在 EDT 中執行。如官方文檔所述,從多線程中調用 Swing 代碼會造成 線程沖突或內存一致性錯誤。
Swing 的線程策略狀態:
Swing 組件必須在 EDT 中創建。
Swing 組件必須在 EDT 中進行訪問,除非您調用文檔化為線程安全的方法。
雖然這看起來很簡單,不過很容易破壞規則。Swing 不為正確的 EDT 使用提 供任何運行時檢查,而且大部分時候表面上 “行為良好” 的 Swing UI 實際上 卻破壞了這些規則。
Swing Explorer 和 FEST-Swing 都支持查找 Swing 線程策略的違規行為。 圖 5 顯示了 Swing Explorer 的 EDT 監視器。EDT 監視器可以在執行應用程序 時報告 EDT 訪問違規行為。
圖 5. Swing Explorer 的 EDT 監視器
FEST-Swing 提供 FailOnThreadViolationRepaintManager 來檢查 EDT 違規 行為,如果檢測到任何違規,它會強迫測試終止。配置很簡單:在標有 @BeforeClass 注釋的 set-up 方法中放入它,如清單 6 所示:
清單 6. 安裝 FailOnThreadViolationRepaintManager
@BeforeClass public void setUpOnce() {
FailOnThreadViolationRepaintManager.install();
}
另外, UI 測試可將 FEST-Swing 的 FestSwingTestngTestCase 或 FestSwingJunitTestCase 分為子類,這兩個類均已安裝了 FailOnThreadViolationRepaintManager。FEST-Swing 也提供有用的抽象類來確 保對 Swing 組件的訪問是在 EDT 中完成的。
GUI 測試失敗故障排除
不管編寫功能 GUI 測試所用的庫是什麼,這種測試都易受到環境相關事件的 攻擊。FEST-Swing 也不例外。例如,一次預定的反病毒掃描可能彈出一個對話 框來阻止正在測試的 GUI。FEST-Swing Robot 將不能訪問 GUI 並最終因超時而 強迫測試終止。測試失敗不是程序錯誤造成的,只是不合時宜而已。
FEST-Swing 的一個非常有用的特性是它能夠在測試失敗時攝取桌面截圖。當 您在 IDE 內執行單個測試時,該截圖會被自動嵌入到 JUnit 或 TestNG 報告中 ,或保存在目錄中。圖 6 顯示了 GUI 測試失敗後的一個 JUnit HTML 報告。注 意測試失敗時 FEST-Swing 添加到桌面截圖中的鏈接。
圖 6. 從失敗測試鏈接到桌面截圖的 JUnit HTML 報告
造成測試失敗的另一個常見原因是組件查詢失敗。推薦的查詢組件的方式是 根據組件的惟一名稱查詢。有時您要測試的 GUI 的組件沒有惟一名稱,這時就 只能使用通用的搜索標准。有兩種類型的組件查詢失敗:
無法找到 GUI 組件。例如,假設您在查詢名稱為 firstName 的 JTextField ,但是原先的開發人員忘記將該名稱賦給組件。在這種情況下,FEST-Swing 在 拋出的 ComponentLookupException 中包含可用的組件層次結構,從而易於發現 失敗的原因。在本例中,您可以檢查組件層次結構以查看 JTextField 是否有正 確的名稱,或是否真正將 JTextField 添加到 GUI。清單 7 顯示了一個嵌入在 ComponentLookupException 中的組件層次結構:
清單 7. 包含組件層次結 構的 ComponentLookupException
org.fest.swing.exception.ComponentLookupException:
Unable to find component using matcher
org.fest.swing.core.NameMatcher[name='ok', requireShowing=false].
Component hierarchy:
myapp.MyFrame[name='testFrame', title='Test', enabled=true, showing=true]
javax.swing.JRootPane[]
javax.swing.JPanel[name='null.glassPane']
javax.swing.JLayeredPane[]
javax.swing.JPanel[name='null.contentPane']
javax.swing.JTextField[name='name', text='Click Me', enabled=true]
清單 7 中的組件層次結構有助於您推斷出原先的開發人員為 JTextField 提 供了錯誤的名稱。當前名稱不是 firstName,而應該是 name。
找到多個 GUI 組件。這種情況會在有多個 GUI 組件匹配給定的搜索標准時 發生。例如,firstName 可能被不小心提供給兩個 JTextField。當查詢名為 firstName 的 JTextField 時,查詢就會失敗(並終止測試),因為兩個組件具 有同一名稱。為幫助您診斷這種問題,拋出的 ComponentLookupException 顯示 所有找到的匹配組件,如清單 8 所示:
清單 8. ComponentLookupException 包含與某個搜索標准匹配的組件列表
org.fest.swing.exception.ComponentLookupException:
Found more than one component using matcher
org.fest.swing.core.NameMatcher[
name='firstName', type=javax.swing.JTextField, requireShowing=false].
Found:
javax.swing.JTextField[name='firstName', text='', enabled=true]
javax.swing.JTextField[name='firstName', text='', enabled=true]
有時,在拋出的 ComponentLookupException 中檢查組件層次結構會很難, 特別是當您在處理含有大量組件的 GUI 時。Swing Explorer 在這裡再次提供很 大的幫助。如上所示,您僅需直接單擊組件就可以選擇和檢查組件層級結構中任 何組件的屬性。大的組件層次結構在 Swing Explorer 的 GUI 中要比由 ComponentLookupException 提供的基於文本的表示要容易理解得多。
結束語
Swing 威力以其復雜度作為代價;理解 Swing 代碼與編寫該代碼一樣有挑戰 性。為探究未知 GUI 代碼而編寫測試比為非可視化代碼編寫測試要復雜。幸運 的是,Swing Explorer 和 FEST-Swing 可以幫助您從這種單調乏味的工作中解 脫出來。通過 Swing Explorer,您可以在應用程序運行時探究 GUI 的結構。一 旦了解了要測試的 GUI 的結構和行為之後,您就可以使用 FEST-Swing 緊湊直 觀的 API 來編寫功能 GUI 測試。除了其連貫 API 之外,FEST-Swing 驗證 Swing 線程和特性的正確使用,這在檢測失敗的 GUI 測試時可幫助您節省時間 。本文只介紹了這對強大工具的一些基本功能。
類似於 Swing Explorer 和 FEST-Swing 的一種更好的解決方案是一種記錄/ 回放工具,它記錄 Java 代碼中的用戶交互,如同它是由開發人員手動創建一樣 。記錄/回放工具在最短的時間內為您提供測試套件。您與現有 GUI 交互,且用 戶生成的所有事件都記錄在一個腳本中。稍後您可以回放該腳本,以為特定場景 重新創建用戶交互。現有記錄/回放工具的主要弱點就是生成測試的維護很昂貴 。應用程序的任何改變都需要重新記錄所有測試場景。同時,在測試與以前類似 的場景時,記錄所有測試場景會創建重復的測試代碼。記錄的腳本通常很長,且 用專用語言編寫,缺乏面向對象(OO)的語言特性。重復動作的模塊化需要很大 的工作量,易於產生錯誤,且通常需要了解一種新的編程語言。
通過使用一個基於一種流行和成熟的 OO 語言 — Java 語言 — 的記錄/回 放工具,開發人員可享受功能豐富的 IDE 帶來的諸多益處,這些 IDE 能將繁瑣 、易出錯的任務(比如代碼重構)變得快速且微不足道,從而可提高效率並降低 維護成本。這正是 FEST 項目團隊目前正專注的工作:開發一種回放/記錄工具 ,它可以使用 FEST-Swing Java API 生成完全基於對象的 GUI 測試。我們希望 在 2010 年第二季度之前能看到該工具。
來源:
http://www.ibm.com/developerworks/cn/java/j- swingtest/index.html