用 .NET 開發的輕量級 UI 測試自動化

James McCaffrey

下載本文的代碼: TestRun0501.exe (131KB)

*
本頁內容
待測試應用程序 待測試應用程序
測試自動化腳本 測試自動化腳本
操作待測試應用程序 操作待測試應用程序
檢查應用程序狀態 檢查應用程序狀態
討論 討論

手動用戶界面測試是一種最基本的軟件測試類型,大多數軟件工程師首次採用的就是這種測試類型。與此矛盾的是,自動化用戶界面測試可能是編寫的測試類型中最具技術挑戰的一種。Microsoft® .NET 環境爲您提供了許多編寫自動用戶界面測試自動化的方式。一種常見而有用的方法是記錄擊鍵、鼠標移動和單擊,然後在應用程序中回放以確保它以預期方式執行。(有關這種方法的詳細信息,請參見 MSDN®Magazine 2002 年 3 月號中 John Robbins 的 Bugslayer 專欄。這一期 MSDN Magazine 的 Paul DiLascia 專欄也闡釋瞭如何使用 .NET 將這種類型的輸入發送到另一個應用程序中。)在本月的專欄中,我將探討爲 .NET 應用程序編寫輕量級 UI 測試自動化的另一種方法。

最好的方式是以一個屏幕快照開始進行討論。圖 1 顯示我有一個虛擬應用程序要進行測試。它是一個顏色合成器應用程序,允許用戶在文本框控件中鍵入一種顏色,然後在 Combobox 中鍵入或選擇一種顏色,單擊按鈕,Listbox 就會顯示一條消息,表明兩種顏色“混合”的結果。在圖 1 中,根據應用程序,紅色和藍色會產生紫色。UI 測試自動化是一個控制檯應用程序,它啓動一個待測試窗體,模擬用戶移動應用程序窗體,定義和調整應用程序窗體的大小,設置文本框和 Combobox 控件的值,並單擊按鈕控件。測試自動化檢查測試應用程序的結果狀態,驗證 Listbox 控件包含正確的消息,並記錄“pass”結果。圖 1 中的屏幕快照是在測試自動化模擬用戶單擊關閉測試應用程序的 File | Exit 之前捕獲的。


圖 1 窗體 UI 測試自動化


在下面的章節中,我將簡要介紹我所測試的虛擬應用程序,解釋如何使用反射和 System.Windows.Forms.Application 類啓動測試自動化程序中的應用程序窗體,介紹如何使用 System.Reflection 命名空間中的方法模擬用戶操作和檢查應用程序狀態,並描述如何擴展和修改測試系統來滿足自己的需要。我想,不管您在軟件生產環境中扮演什麼樣的角色,具備快速編寫輕量級 UI 測試自動化的能力都能使您的技能得到很大提高。另外,即使您正在使用一個已有的框架(如 Nunit),這些相同的技術也可以整合到您自己的單元測試管理中並相關。

待測試應用程序


讓我們來看一下待測試應用程序,以便理解測試自動化的目標。待測試顏色合成器應用程序是一個簡單的 Windows® 窗體。應用程序的代碼是使用 C# 編寫的,但我將向您介紹的 UI 自動化技術適用於用任何以 .NET 爲目標的語言編寫的應用程序。我接受 Visual Studio® .NET 默認控件名稱 Form1、textBox1、comboBox1、button1 和 listBox1。當然,在實際的應用程序中,您應該更改控件的名稱來反映它們的功能。我添加了三個虛擬菜單項:File、Edit 和 View。圖 2 中列出的代碼是待測試應用程序的主要內容。

當用戶單擊 button1 控件時,應用程序就會獲取 textBox1 和 comboBox1 控件中的值。如果這兩個顏色字符串匹配,就會顯示這種顏色的消息。如果文本框和 Combobox 控件分別包含“red”和“blue”,則顯示結果消息“purple”。如果文本框和 Combobox 控件中爲其他任何顏色組合,則顯示結果消息“black”。因爲這只是用於演示的虛擬應用程序,我想讓代碼儘可能簡短,所以沒像在實際應用程序中那樣檢查輸入參數。雖然這個應用程序非常簡單,但它具備了演示自動化 UI 測試所需要的基於 Windows 應用程序的大多數基本特徵。

即使對於如此小的應用程序,要手動測試它的用戶界面也是很繁瑣、易出錯、耗時且又低效的。您必須鍵入一些輸入,單擊按鈕控件,直觀驗證結果消息,並將結果記錄到 Excel 電子表格或其他數據存儲中。因爲應用程序接受用戶在文本框控件中的自由輸入,實際上可能的測試輸入是無限的,所以您必須測試上百甚至上千個輸入才能很好地理解應用程序的行爲。對於上述所有操作,一旦應用程序代碼有所更改,您就必須從頭執行相同的手動測試。編寫單元測試是一個更好的方法,因爲這些測試允許您模擬使用該應用程序的用戶,然後確定應用程序是否正確響應。

測試自動化腳本


圖 3 顯示了測試自動化管理的整體結構,圖 4 顯示了代碼大綱。這裏我使用了 C#,但您可以很容易地將代碼修改爲任何基於 .NET 的語言。


圖 3 UI 測試自動化結構


我首先添加和聲明 System.Windows.Forms、System.Reflection 和 System.Threading 命名空間的引用。因爲默認情況下控制檯應用程序不引用 System.Windows.Forms.dll,所以要使用這個命名空間中的類,就需要添加對 System.Windows.Forms.dll 文件的項目引用。System.Windows.Forms 命名空間包含 Forms 類和 Application 類,它們在這個解決方案中都有使用。我使用 System.Reflection 中的類來獲取和設置窗體控件的值並調用與該窗體相關的方法。使用 System.Threading 中的方法來從控制檯應用程序測試管理啓動窗體。

我聲明瞭三個類作用範圍對象,因爲在測試管理中有幾個方法要用到它們:


因爲顏色合成器應用程序只是一個 Windows 窗體,所以我聲明一個 Form 對象來表示它。System.Reflection 命名空間中有許多方法將 BindingFlags 對象作爲篩選器使用。我爲 Thread.Sleep 方法設置了一個值爲 1500(毫秒)的整型延時變量,這樣在測試自動化的各個時刻都可以暫停 1.5 秒。我使用這段代碼來啓動待測試應用程序:


我定義了圖 5 中的 LaunchApp 方法及其 helper 方法 RunApp。這段代碼的行數不多,但作用很大。請注意,爲了簡單起見,我硬編碼了指向待測試應用程序可執行文件的路徑(在您自己的測試中,您可能想用參數表示這個信息以使得測試自動化更加靈活)。LaunchApp 方法接受應用程序可執行文件的路徑和應用程序窗體的名稱,並返回一個表示該窗體的對象。LaunchApp 使用 Assembly.LoadFrom 靜態方法創建 Assembly 對象的實例,而不是通過顯式調用構造函數來創建。

接下來,Assembly.GetType 方法返回一個表示應用程序窗體的類型,然後我使用 Assembly.CreateInstance 方法創建對待測試窗體的引用。然後我發起一個新的線程來實際啓動應用程序窗體。Application.Run 方法開始在當前線程進行消息循環;由於我想在窗體可見時執行工作,所以我需要使 Application.Run 在自己的線程中運行,這樣循環纔不會阻止我的進程。通過使用這種技術,測試自動化控制檯應用程序管理和窗體就會在不同線程但相同的進程下運行。這種方式可以使它們相互通信 — 也就是說,測試管理可以發送指令給 Windows 窗體。

操作待測試應用程序


當啓動待測試應用程序後,我模擬用戶操作應用程序窗體。示例測試方案是從移動窗體和調整窗體大小開始的,如下所示:


SetFormPropertyValue 方法執行了所有工作(請參見圖 6)。我使用 Object.GetType 方法創建表示應用程序窗體的 Type 對象,然後使用該對象獲得引用窗體屬性(例如 Location 屬性或 Size 屬性)的 PropertyInfo 對象。一旦擁有屬性信息對象,就可以使用 PropertyInfo.SetProperty 方法來操作它。SetProperty 接受三個參數。前兩個可能是您想要的 — 對包含要更改屬性的對象的引用和對新屬性值的引用。

第三個參數是必需的,因爲有些屬性(例如 Listbox 控件的 Items 屬性)是索引的。我這裏所做的移動窗體和改變窗體大小實際上與測試應用程序功能並不相關,但我想通過它告訴您如何實現以防您的測試方案需要。還要注意,我正在使用的是 Form 類(實際上是它的 Control 基類)公開的 ISynchronizeInvoke 接口。您應該只通過擁有控件底層窗口句柄的線程訪問(包含 Form)控件的屬性。對於待測試窗體,該線程就是爲運行 Application.Run 而發起的線程。由於我的測試管理是在單獨的線程中運行的,所以我需要將對控件的屬性和方法的訪問封送到該線程,使控件的 Invoke 方法和 InvokeRequired 屬性成爲一體(該方法和屬性都是 ISynchronizeInvoke 接口的一部分)。有關 IsynchronizeInvoke 的更多信息,請參閱 MSDN Magazine 2003 年 2 月號中 Ian Griffith 的文章:Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads

現在我準備模擬用戶在文本框控件中鍵入一種顏色,並從 Combobox 控件選擇一種顏色:


我先將 textBox1 設置爲“yellow”,再將其設置爲“red”,然後將 comboBox1 設置爲“green”,再設置爲“blue”。所有的實際工作都是由圖 7 中所示的 SetControlPropertyValue 方法完成的。

我使用 Thread.Sleep 方法來暫停測試自動化以確保待測試應用程序已啓動並運行。創建表示應用程序窗體類型的 Type 對象之後,我使用 Type.GetField 方法檢索 Form 對象中指定字段(控件)的信息。然後調用 FieldInfo.GetType 方法獲得表示想要操作的控件的 Type 對象。一旦擁有控件對象,我就可以像操作 Form 對象那樣操作該控件對象,即獲取控件的 PropertyInfo,然後調用 SetValue 方法。對於 SetFormPropertyValue,需要確保所有的屬性更改都是在正確的線程中進行的。

請注意,測試自動化並不直接模擬極低水平的用戶操作。例如,自動化並不模擬對 textBox1 控件的各次擊鍵,而是直接設置 Text 屬性。同樣,自動化不模擬對 comboBox1 控件的點擊,而是設置 SelectedItem 屬性。這是我的測試自動化系統的一個設計缺陷。要採取那種方式測試,您可以按照 John Robbins 在前面提到的文章中的建議來做。

模擬用戶在文本框和 Combobox 控件中鍵入顏色的操作之後,自動化會模擬點擊按鈕控件:


我已經定義瞭如圖 8 所示的 InvokeMethod 方法。

InvokeMethod 通過調用 Object.GetType 方法獲得表示待測試應用程序窗體的 Type 對象。然後我使用 Type.GetMethod 獲得指定方法的信息,並調用 MethodBase.Invoke 執行指定方法。Invoke 接受兩個參數。第一個是要對其調用方法的窗體實例,第二個是方法的參數數組。對於按鈕控件單擊方法這種情況,簽名如下所示:

private void button1_Click(object sender, System.EventArgs e)爲了滿足 button1_Click 方法的參數需要,我需要傳遞一個表示發送方的對象和一個表示可選事件數據的 EventArgs 對象。對於按鈕單擊,我忽略了第一個參數的值,雖然對於實際的測試系統,我應該將控件作爲導致調用該方法的發送方加以傳遞(該方法的實現可能依賴於對控件的訪問,如果這個事件處理程序方法被當作多個控件的處理程序使用,則這個信息就特別有用)。對於第二個參數,我傳遞一個空的 EventArgs 對象。

請注意,測試自動化是通過直接調用按鈕控件的相關方法模擬按鈕單擊的,而不是通過觸發事件模擬。當實際用戶單擊按鈕時,它會生成一個 Windows 消息,該控件對該消息進行處理,並將其轉換成託管事件。這個事件會導致調用一個特定的(或一組)方法。所以如果應用程序爲按鈕單擊事件關聯了錯誤的方法,UI 測試自動化不會捕捉到邏輯錯誤(雖然每次測試都會失敗,而且您會很快發現問題)。這個問題可以糾正,方法是獲取使用反射的事件的底層多路廣播委託,然後當事件引發時使用委託的 GetInvocationList 方法來獲得要調用的所有委託的列表。然後可以單獨調用每個委託。或者可以使用事件的 EventInfo 及其 GetRaiseMethod 方法來獲取引發事件的方法的 MethodInfo,但這樣做只返回一個自定義的引發方法,而且支持自定義引發方法的 Microsoft 語言只有 C++ 和 Microsoft 中間語言 (MSIL)。再次說明,所有這些問題都可以通過使用前面討論的 send keys 方法加以避免。

檢查應用程序狀態


在自動化通過模擬用戶鍵入和單擊來設置應用程序窗體的狀態之後,就可以檢查系統狀態來查看應用程序是否正確響應了(請參見圖 9)。

我設置了一個名爲“pass”的布爾變量,將其設爲 true — 我假設應用程序的狀態是正確的並檢查該狀態,如果有某個地方出錯,則將 pass 設爲 false。檢查 textBox1 控件,確保其 Text 屬性正確地設爲“red”。然後檢查以確保 comboBox1 的值爲“blue”,而且 listBox1 顯示正確的消息“Result is purple”。如果一切檢查都通過,則打印 pass 消息,否則打印 fail 消息。

檢查應用程序系統狀態的關鍵是我編碼的 GetControlPropertyValue 方法,如圖 10 所示。首先使用 Object.GetType 創建表示應用程序窗體的 Type 對象。然後使用 Type.GetField 獲取指定控件的信息。接下來再使用 GetType 獲取表示控件的 Type 對象。最後使用 GetProperty 獲取控件的指定屬性的信息,並使用 GetValue 方法獲取控件屬性值。GetValue 需要一個索引對象參數,因爲屬性可被索引(例如,當我試圖獲取 Listbox 控件的 Items 屬性時)。

請注意,檢查 listBox1 控件中的文本比檢查 textBox1 控件中的文本要靈活一些。我使用我的 GetControlPropertyValue 方法訪問 Items 屬性,然後使用 Contains 方法進行檢查。

檢查應用程序狀態並記錄 pass 或 fail 結果之後,我就可以輕鬆地退出待測試應用程序:


雖然當測試管理終止時待測試應用程序也會終止,因爲它們是在相同的進程中運行的,而且待測試應用程序是在後臺線程中運行的,但爲了顯式清理分配的任何系統資源,最好是顯式通過測試管理退出應用程序。

討論


如果您在 .NET 問世之前就想實現 UI 測試自動化,則實際上只有兩種選擇。第一,購買商業化的 UI 自動化工具。第二,使用 Microsoft Active Accessibility (MSAA) API 創建自己的 UI 自動化工具。我所介紹的系統很好地補充了另外兩種策略。有幾種優秀的商業化 UI 自動化工具可供您使用。這些工具的優點是它們的功能齊全。而缺點是您需要爲它們付費,有一段陡峭的學習曲線,而且當您需要修改功能時不允許您訪問源代碼。使用 MSAA 可以讓您完全控制您的自動化工具,但它需要很長的時間去學習。實際上,在我做過的幾個項目中,基於 MSAA 的 UI 測試自動化很可能像待測試應用程序那樣複雜!

我這裏提到的自動化 UI 測試方法已經成功地在幾個大中型的產品中使用。由於它可以快速輕鬆地實現,所以當待測試系統很不穩定時,可以早早地將它用於產品週期中。然而,由於這個 UI 測試系統相對輕量級,所以它無法應付所有可能的 UI 測試情況。您可以採用多種方式修改和擴展這一設計。因爲此次介紹的意圖是讓讀者有個初步的瞭解,爲了更加清楚和簡單,我刪除了大部分錯誤檢查並對大多數信息進行了硬編碼。有必要向測試自動化添加許多錯誤檢查代碼 — 畢竟您期待的是發現錯誤。

根據您的生產環境,您可以對測試系統的一些部分進行參數化。在測試術語方面,我提到的系統稱爲測試方案 (test scenario) — 控制待測試應用程序狀態的一連串操作(與測試用例 (test case) 不同,後者通常指較小的操作,如傳遞一個參數給方法,檢查返回值。)例如,要參數化該方案,您可以創建一個如下所示的輸入文件:


然後您可以讓您的測試自動化讀取、解析和使用該文件中的數據。您還可以使用 XML 或 SQL 作爲測試方案輸入數據。我所介紹的測試系統將其結果記錄到命令外殼中。您可以很輕鬆地通過命令行將結果重定向到文本文件,或者重寫自動化來直接記錄結果。

下一代 Windows(代號爲“Longhorn”)將引入一個代號爲“Avalon”的新的表示子系統。Avalon 會將我介紹的 UI 測試自動化概念提到更高的層次。我們亟待爲所有 UI 元素的自動化提供平臺級別的支持,併爲所有用戶控件公開一致的對象模型。這樣可以讓開發人員和測試人員快速輕鬆地創建極爲強大的 UI 測試自動化。本專欄中的技術是對革命性的 Avalon 的工作方式的一個提示。

在 .NET 之前,編寫自動化通常是一個很耗資源的任務,而測試自動化(特別是 UI 測試自動化)通常被置於產品任務優先級列表的最底層。但有了 .NET 之後,只需要花費以前所用時間的一小部分,就可以編寫出非常強大的測試自動化。


請將您要給 James 的問題和建議發送到 [email protected]

James McCaffrey 就職於 Volt Information Sciences, Inc.,負責對在 Microsoft 工作的軟件工程師進行技術培訓。他參與開發多種 Microsoft 產品,包括 Internet Explorer 和 MSN Search。您可以通過 [email protected][email protected] 與 James 取得聯繫。

轉到原英文頁面

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章