如何自動測試使用者介面?

作者:小宇

前言

        最近流行一種軟體設計方法,叫作XP(eXtreme Programming),這種方法強調軟體開發過程中,測試要比開發優先,並且讓測試自動化,每天不斷地測試,可確保軟體每次改版的品質,品質提升後,可以節省許多時間與成本(尤其是維護方面),並且不怕面對客戶需求的變更,也不怕為了良好架構而對程式碼所做的重整(Refactoring)

JUnit測試GUI的問題

        XP這套方法開始普遍受到軟界的重視之後,許多軟體開發者為了實現自動化測試的目標,開發了許多工具。JavaProgrammer建立了一個叫作JUnit類別,若繼承此類別來實作測試程式,就可以用測試軟體來批次執行所有的JUnit。這種方法主要是用來測試軟體的Logic,但對於GUI的測試要如何進行呢?Test-Driven Development In .Net,這篇文章有提到必須為每個GUIView Class實作存取資料用的Interface,細節請參考該篇文章。這種做法對於擁有許多視窗介面的軟體來說,要撰寫的測式程式想必就很繁瑣。

解決方案

        為了解決這個問題,目前市面上有許多自動化測試GUI的軟體,如QuickTest,可將測試的流程寫在腳本檔堙A然後執行腳本檔來進行GUI的測試。關於自動化測試與相關軟體的介紹,可參考網站:http://www.oldsidney.idv.tw,堶惘閉蛪簋袨I的資料。本文主要是簡單地介紹自動化測試GUI概念,並用一個範例說明測試腳本在軟體開發的生命週期中,能帶來什麼樣的好處。

 

GUI的測試,比較好的做法就是用簡單的腳本語言取代一般的程式語言來寫測試程式,這樣可減輕一些測試程式的維護成本。為什麼可以用簡單的語法來寫測試程式?因為大部份的軟體,都是Window-based環境下開發的。軟體不管設計的多複雜,從使用者與GUI之間的關係來看,就只是使用者發送一連串的訊息給GUI而已!若將這個使用者換成機器人,由機器人發訊息給GUI,不就是完成自動化測試?所以Programmer可以事先要將發送的訊息寫好存成一個腳本檔,這種腳本檔的格式類似Table-Driven Testing的樣子(參考Table Driven Testing: Making Automation Accessible ),是以ItemOperationValue這三個欄位描述使用者的每一項動作,再丟給機器人以非常快的速度依腳本執行,以取代人工的測試!

以開發小算盤為例

        舉一個例子,在為客戶開發一支小算盤程式,和客戶討論該有那些功能後,我們會先設計GUI長如下這個樣子(Windows的小算盤作為例子),然後和客戶確認GUI各項功能細節。(本文初稿寫完之後才發現,已經有人舉過類似的例子了:Choosing a test automation framework )

 

 

 


在開始Coding之前,依循XP精神,先寫測試程式。若是用腳本語言來寫,則稱為測試腳本(Test Script)。寫法如下:

  Timer = 10

 

  b_Cancel 

  b_1

  b_Add

  b_2

  b_Enter

 

  verify =

  [

    result = 3

  ]

腳本語法解說:

1.          Timer = 10 表示這個腳本會以每行0.01秒的速度執行。

2.          b_Cancelb_1b_Addb_2b_Enter,都是畫面上按鈕的名稱,開頭「b」的Button的縮寫。開發程式在為元件命名時,考量到為了讓測試腳本容易編寫,會盡量將名稱取簡短並且易讀。在腳本堳名元件名稱,若測試程式讀到元件的名稱,當發現它是一個Button時,就會對該按鈕觸發Click事件。

3.          verify指令是用來驗證結果,為了要讓軟體在執行某項功能後的結果能夠被驗證,必須將執行該項功能的程式後面加上一段能將系統目前重要的狀態存下來的字串,這個字串會被存下來,供測試程式與verify堛漲r串比對,看看是否一樣,若不一樣,就表示程式或測試程式有Bug,則該項測試就不通過。

 

Delphi來簡單解釋測試程式是如何驗證軟體的執行結果:

 

procedure TForm1.b_EnterClick(Sender: TObject);

begin

  ....進行各種運算的程式碼

  verify := verify += Format(‘result = %d’, [result]);

end;

 

上面是軟體的一段程式碼,用到verify這個global字串變數。若軟體是被測試程式驅動的,當測試程式執行腳本讀到 b_Enter時,會對b_Enter觸發Click事件,作業系統收到這個事件後,會去執行b_EnterClick這個Method。該Method最後會將運算後的值記錄在verify字串堙C測試程式讀到下一行 verify時,就會將它與系統的verify變數比對看是否一樣。若相同,則表示測試通過。


從Use Case的觀點來看,這個腳本是加法運算的主要流程。我們還要撰寫一些腳本來模擬例外的狀況(即加法運算的替代流程):

  Timer = 10

 

  b_Cancel 

  b_Add

  b_2

  b_Enter

 

  verify =

  [

    result = 2

  ]

這個腳本是進行 0 + 2 = 2的運算,請注意, 並沒有將b_1這個鈕寫進去。這表示使用者就只按「+」鈕,再按「2」鈕,不管使用者會不會這樣用,一個完善的系統應將各種可能的狀況考慮進去,所以我們先為這個例外情況寫了測試腳本。

 

另一個替代流程是,b_1與b_2都沒被按到,只按「C」、「+」與「=」,計算結果為零。我們在寫這種例外的腳本時,會問自己或客戶這種結果是否合理?若合理,則列為加法運算使用案例的替代流程:

  Timer = 10

 

  b_Cancel 

  b_Add

  b_Enter

 

  verify =

  [

    result = 0

  ]

目前為止,我們已經建立3個測試腳本檔了,檔名分別是:

「01 – 加法運算 – 主要流程.txt」

「02 – 加法運算 – 替代流程 - A.txt」

「03 – 加法運算 – 替代流程 - B.txt」

檔名前加上編號,是為了讓批次測試軟體依照同一目錄下的編號順序執行腳本檔。這在後文會有詳細的說明。

 

我們可能會從加法運算的03這種特殊的測試案例,擴充到所有運算,這樣寫出來的軟體才算完備,例如除法運算:

  Timer = 10

 

  b_Cancel

  b_Div

  b_Enter

 

  verify =

  [

    result = 函數結果未定義。

  ]


這個腳本,會執行 0 / 0 的運算,這個結果會出現「函數結果未定義。」的訊息。我們可以將這個腳本存檔為「03 – 倒數運算替代流程 – B.txt」放在「除法運算」的目錄下。
 

我們可以將加法運算的所有腳本複製後,修改成符合除法運算的腳本,於是在我們的Unit Test目錄下有這些腳本檔:

 

[Unit Test]

        [1. 加法運算]

                01 – 加法運算主要流程.txt

02 – 加法運算替代流程 - A.txt

03 – 加法運算替代流程 - B.txt

        [2. 除法運算]

                01 – 除法運算主要流程.txt

02 – 除法運算替代流程 - A.txt

03 – 除法運算替代流程 - B.txt

 

以這樣的檔案結構來安排腳本檔的好處在於,測試程式就可以選其中一個目錄或某目錄下一個或多個檔案來執行腳本檔。

 

現在我們可以開始為這6個測試檔撰寫程式了。我們可能會以OO的方式來設計系統,或以傳統結構化的方式來設計,甚或不做任何設計上的考量,就直接開始Coding了。不管程式怎麼寫,最後的結果都要能滿足這6個測試檔。第一個能夠滿足這些測試檔的版本寫出來之後,若覺得程式碼有點亂,想要做重整(Refactoring),這個時候就可以開始做了。當然,重整後的程式,也必須能通過測試,才算重整成功。

 

在這段開發週期結束之前,必需將軟體交給客戶驗收。於是我們跑到客戶面前,將所有測式腳本跑過一遍(執行腳本的速度可以調慢),同時向客戶解說(或教育訓練),並向客戶確認每次測試的結果是否為客戶當初所預期的。因為我們事先準備了腳本檔,可以教學錄影帶一樣播放系統運作的情形,這樣可以節省許多教學的時間。

 

實際運用情形

        因為本人所參與的專案都是用Delphi開發的,所以針對Delphi開發的應用軟體,建立一套可執行腳本的表格驅動測試系統(Table-Driven Testing System)。這套系統目前只用來測式Delphi開發的Windows程式,不過理論上這套系統應該可以擴充至任何語言開發的Windows應用軟體上,甚至Web也行,就像其它的測式軟體一樣。因為在Windows環境下執行的軟體都是靠訊息驅動的,只要能發送適當的訊息,給適當的視窗上的元件,就能進行GUI的自動化測試。這一點我就沒有再做更深入的研究,所以目前TDT只適用於Delphi開發的應用軟體。

 

TDT的畫面如下圖所示:

 

與其它JUnit的測試程式不同,TDT就只有這個簡單的視窗,這個視窗可以選取測試腳本檔,或目錄來做批次執行。將測試程式與測試腳本分開來的好處有:

1.          這樣一來,只要改腳本檔,不用改測試程式,就可以馬上執行測試了。

2.          以簡單的語法來撰寫測試的流程,這與以往用程式語言如JavaDelphi來寫測試程式相比,會更為精簡。

3.          對使用者做教育訓練時,可以較慢的速度執行這些腳本檔。針對使用者不懂的地方可以隨時重覆播放。在講解時也會更有信心,因為事先都已經跑過好幾遍了!

4.          腳本檔經過轉換後可做為教育訓練的教材文件,所以可以不必另外再寫教材,直接從腳本檔轉換即可。這是以往用程式語言來寫測試程式所難以做到的!

 

結論

直接從GUI測試,就可以Cover大部份LogicUnit Test了!因為對於使用者來說,GUI才是他們使用軟體的介面。所以不管軟體內部的Logic如何設計,只要GUI的執行結果正確,軟體才算是沒問題。以往JUnit大部份是用來測試軟體的Logic,所以在設計軟體時,就要將GUILogic分開。雖然有人提出JUnit也能用來測試GUI,但要透過間接的方法(OO的技術)GUI轉換成Logic來測試。但若能直接自動化測試GUI,是否就可以完全省略Logic的測試?以本人開發專案的經驗來說,大約90%可以這樣做。剩下的10%無法這樣做的原因是提供給其它系統呼叫的Interface是沒有GUI的,正確地說,GUI是長在其它系統身上的,我也不知道是什麼樣子,只有該系統的設計者知道!為了確保提供給其它系統的Interface是沒有Bug的,這時候傳統的JUnit方法就派上用場,不過通常我會自己寫個GUI好讓TDT測試!

 

日後有機會的話,我再為TDT的系統架構與自己設計的腳本語法作更深入的探討!