1. 程式人生 > >使用 IBM Rational Functional Tester測試 Windows 應用程式: 如何構建結構良好的測試框架

使用 IBM Rational Functional Tester測試 Windows 應用程式: 如何構建結構良好的測試框架

一個好的測試框架需要具備哪些元素呢?雖然對不同的專案而言,答案可能有所不同。但總的來說,一個好的測試框架通常具有以下的共同特點:

  • 分層結構
  • 關注分離
  • 程式碼重用
  • 結構清晰
  • 易於維護
  • 方便除錯
  • 可擴充套件性好

除了以上所述的幾點外,一個好的框架還應該提供相應的通用服務,以使得指令碼開發者可以很容易而且快速地基於它來開發指令碼。比如象錯誤處理,本地化版本支援,日誌服務等。

如何定義一個良好的層次結構,是我們在構建測試框架首先需要考慮的問題。通常我們會把最基本的一些原子操作放在最底層,而在較上層封裝這些操作,並開放相應的介面供最終的指令碼開發者進行呼叫。這樣往往會使得 Case 更加簡潔易讀。

根據面向物件基本理論,我們首先要定義一些基礎的控制元件類,用來代表那些需要在測試中進行操作的基本介面元素,象按鈕,輸入框等。如果你使用 RFT 測試 Java 或 Web 應用,那麼你可以直接使用 IBM Package,在這個包裡封裝了所有的在 Java 和 Web 這兩個 Domain 下的基本控制元件。通過在你的指令碼中呼叫這些類,你就可以很方便地實現對被測應用的操縱。但是,我們所測試的應用是基於 Windows .Net 的,在這個包裡沒有對應的控制元件類可以利用,因此,第一步,我們要開發自己的控制元件類。

其實,我們也可以不定義基礎類,而直接使用 GUITestObject 來操縱每一個介面物件,但這樣做的代價是顯而易見的,會有很多重複且不易讀的程式碼充斥在我們的指令碼中。這顯然不是我們所想要的。好在雖然各種控制元件的種類繁 多,但對於每一個控制元件而言,需要封裝的主要是一些我們在測試中經常需要呼叫的簡單方法,比如說 Click(),或 SetText(),因此工作量並不太大。

在實現了這一層之後,理論上說,我們可以直接在 Case 腳本里呼叫這些方法來實現對測試物件的操作。但這樣做也有一個問題是,如果以後你對這些方法的名稱進行了修改,或者因為程式實現的改變,原來的 A 類控制元件被 B 類代替,這時,勢必會對所有已經編寫好的 Case 指令碼造成很大的影響,帶來很大的程式碼維護量。因此為了隔離下層程式碼對上層 Case 的影響,我們在其中又加入了新的一層。最後的層次結構如下:

以下是對每一層的具體解釋:

  • NetWidgets:這一層封裝了所有的基本 Windows 控制元件,它與具體的應用無關,可以看作是測試 .Net 應用的基礎類庫。對應於 Windows 的每一個控制元件,在這一層裡,都有一個對應的 Class,其中包括對該控制元件的基本操作,以及一些在測試過程需要使用的驗證方法,比如驗證某個屬性的值。
  • AppLib:所有與當前測試應用相關的方法及物件在這一層定義。它包括 2 部分:
  • Dialogs:正如名稱所示,在這一部分中,主要儲存所有的對話方塊或其他視窗物件,以及對這些視窗上控制元件的操作方法。他們通過呼叫下一層的基本控制元件函式 來實現。與 NetWidgets 不同,在這一層定義的方法,是與應用中具體的控制元件名稱所繫結的,具有明確的含義,如 SetPassword();而在 NetWidgets 層中,僅僅是某一類控制元件的一個通用的方法而已,如 SetText()。除了視窗物件外,一些其他的經常需要乃至的物件,如應用中的選單,工具欄等,也在這一部分定義。
  • Wizards:在這一部分所定義的方法,他們包含是一些基本操作的組合,用來完成某項具體的功能,如 Login()。這些功能都是大部分 Cases 需要經常呼叫,或者是特定的測試步驟組合。通過將它們納入這一層統一管理,可以很方便地起到程式碼重用的目的。通常這些 Wizard 是對同一個 Dialog 上的操作組合,但這並不是必須的。有時候跨多個 Dialog 的組合操作在測試用例中也是很常見的,因此也需要歸納從而提高重用性。
  • TestCases:很明顯,這是最上一層,也就是儲存所有測試指令碼的地方。通過呼叫中間 AppLib 層的方法來實現所有測試功能,包括執行操作,檢查狀態並記錄測試結果。

在描述完整個框架的大致結構後,我們來看看這種結構的優點:

  • 程式碼隔離。通過引入 AppLib 中間層,對最上層 TestCases 遮蔽最底層實現,也就是將測試邏輯與具體功能實現邏輯分開。TestCasse 層只關注具體的測試步驟,而 NetWidgets 完成最終的功能實現。通過這種方式,使得 TestCases 層的程式碼簡單明瞭,可讀性好;另外未來對 NetWidgets 的改變將不會對 TestCases 層造成任何影響,也就是提高的程式的可維護性。
  • 結構清晰。各層所定義的方法及功能也十分明確。每一層僅通過呼叫其直接下層來實現功能,禁止跨層呼叫保證程式碼之間的多重依賴。這對編碼除錯或測試執行時對問題的快速定位很有幫忙。
  • 可擴充套件性好。軟體產品總是在不斷地發展,新功能不斷地引入。在使用這種三層結構的框架以後,當需要增加新的 Cases 時,只需先加入對應的對話方塊及視窗物件到 AppLib,然後再通過呼叫它們來完成程式碼編寫。新加的 AppLib 物件不會對原有的物件造成任何影響,因為每個物件都有僅屬於自己的程式碼檔案。而原有的 AppLib 物件則可以簡單地在新的 Case 中重用。

採用這樣的框架,只開放相應的介面供最終的指令碼開發者進行呼叫,會使 Case 更加簡潔易讀。在 GUI 介面發生變化時,也不需要對 TestCase 做任何修改,大大提高了程式的可維護性和可擴充套件性。

在定義完整個測試架構以後,接下來是在測試工具中用程式碼加以實現。在本專案中,我們使用的是 RFT,現在,讓我們來看看在 RFT 裡的具體實現。

從上圖可以看到,我們使用 3 個 Project 來對應 3 個不同的層,而不是象通常的專案一樣,將所有的程式碼放在一起,層次結構通過不同的包結構來體現。這樣做的主要原因是讓各層之間更加獨立,而且更易於管理。 另一個好處是,多個 Project 的設計可以讓複製和共享更加靈活。例如,當其他 Team 也想要利用 NetWidgets 時,只需要簡單地將對應的 Project 共享給他們即可;而另一個 Team 如果需要某一個 Suite 來驗證某項具體功能,則可以將 3 層所對應的 Project 都共享給他。

Dialogs 和 Wizards 目錄中定義了所有與具體被測應用相關的方法,而位於 Suite Project 中的 Test Case 則呼叫他們來進行測試。在這裡我們列出一些程式碼以便大家能夠更清楚地瞭解它們之間的呼叫關係。

這是一段摘自 LoginDlg.java 的程式碼(Dialog):

這是一段摘自 Login.java 的程式碼(Wizard):

從上面的程式碼可以看出,在 Dialog 物件中,所定義的方法都是對該 Dialog 中控制元件的基本操作方法,象點選一個按鈕,在編輯框裡輸入字串等。但在 Wizard 中,則通過將這些 Dialog 中的基本操作串聯起來完成一個簡單任務,如登入。相類似的功能則放入同一個 Wizard。

通過在框架中提供一些公用服務,可以使得該框架更加易於使用,並且功能強大。在前面的圖 2 中,可以看到在 Utils 目錄下有 3 個 Class。它們分別提供不同的功能,讓我們做一下簡單的介紹。

對於自動化測試而言,有一個很重要的環節是用一種統一的方式來記錄測試的結果,從而可以方便地進行統計或生成報表。所以第一個要提到的是 TestResult。

這個 Class 用來記錄每個 Case 的名稱和執行結果。它讀取一個 Xml 模板檔案,這個檔案定義了哪些內容需要在測試結果中顯示。在自動執行完成後,包括 Suite 名稱,測試環境,Case 資訊,測試結果等這些資料將會被寫入,生成結果檔案。最後通過一個事先定義好的樣式表文件進行格式化,用網頁的形式呈現。

另外,對於失敗的 Case,最主要的錯誤資訊及螢幕截圖也會一併記錄下來,以方便進行分析查錯。

LibException

這個 Class 作為所有執行時異常的基類。這個父類裡增加了以下功能:

  • 當有錯誤發生時,抓取螢幕截圖,儲存到本地檔案,同時將檔案路徑寫入一字串屬性中。
  • 當有錯誤發生時,儲存錯誤的 Stack trace 到檔案中,同時將檔案路徑寫入一字串屬性中。

通過這些功能,結合使用上述的 TestResult 類,就可以很方便地儲存每個錯誤發生時的詳細資訊,而不僅僅是一個簡單的錯誤訊息。

一般來說,在 RFT 測試中,通常使用 Object Map 來在回放中定位介面物件。Object Map 是在測試指令碼錄製中自動生成的,因此使用起來較為方便。但它有一個缺點是,它記錄了所有物件的層次結構,並據此進行查詢。一旦物件的關鍵屬性或層次發生改 變,則必須重新錄製以更新 Object Map。當專案裡 Case 比較多的時候,這就變成了一項費時費力的工作。因此,RFT 還提供了另一種動態查詢的方法 TestObject.Find(),在編寫指令碼時可以即時地呼叫該函式,通過給定的屬性值進行查詢定位。目前它的效率已經與通過 Object Map 定位不相上下。

ObjectFinder 類是一個用來動態查詢物件的工具類,它通過呼叫 TestObject.Find() 來實現,並提供多種不同的查詢方法。最常見的是指定物件的 .class 和 .text 屬性來在某個視窗中進行查詢。這個類主要在 AppLib 層中的 Dialog 物件中使用,在那些對視窗中物件進行操作的函式中,第一條語句很可能就是呼叫 ObjectFinder.getObject(className, captionText, parent) 來找到所要操作的介面物件。

另一個使用動態查詢的好處是,可以將那些用於識別介面元素的關鍵屬性儲存到一個配置檔案中。當有屬性值變化時,就可以很簡單地通過修改配置檔案實 現,而無需修改指令碼。同時,如果需要進行其他語言版本的測試,也可以直接將配置檔案替換為其他語言版本的檔案來實現對多語言版本測試的支援。在我們這個項 目中,我們還有另外一個工具可以直接從原始碼的資原始檔中提取介面上的文字資源,自動生成我們所需的屬性識別配置檔案中,從而大大簡化了手工編寫的工作量, 同時也可以輕鬆應對因介面文字改變而帶來的查詢修改的困擾。

結束語

開發一個適合自己專案的 RFT 自動化框架,需要了解架構設計方面的基礎知識,以及清楚地知道一個好的測試框架所需要提供的功能。但這並不很困難,因為有很多這方面的文章和經驗可供參考,同時也有很多成熟的框架可資借鑑。

除了結構清晰,關注分離,易於擴充套件之外,一些通用服務也是一個好的測試框架不可或缺的部分。通過利用這些服務,RFT 的指令碼開發人員可以更方便、快捷地開發出自動化指令碼,同時保證使用統一的方法,生成格式一致的測試結果。這些也是使用框架的意義所在。

轉載於 IBM: