天天看點

使用JUnit高效完成功能測試

  大多數java開發人員都善于解決邏輯結構測試問題,比如如何建立測試預設環境、利用斷言?添加測試方法、用setup方法進行初始化等。然而,如果java開發人員能更深入地了解如何設計功能測試集來有效地檢驗代碼是否正常運作,他們将獲得更多的益處。

  确定測試用例覆寫所有程式行為。

  确定代碼入口點:測試程式整體功能的主要代碼段。

  比對入口點與相應的測試用例。

  根據初始化 /運作/檢查流程建立測試用例。

  設計并利用運作時事件表進行測試。

  我将結合saxon(一個可以處理xpath、xquery和xslt 的xml工具)的源代碼來具體闡述這些政策。saxon由約50000行java代碼組成,它是開源的,代碼風格優良,注釋文檔詳盡。

  确定用例

  功能測試有兩個相輔的目标:覆寫率與粒度。為確定完整性,功能測試必須覆寫程式提供的所有功能,且必須在各元件水準上分别進行測試。一個測試可以建立在另一個測試的基礎上,但任何測試都不能用來驗證兩項功能。

  一個典型的企業java程式應該包含各種使用者所需的詳細文檔,包括用例說明、非功能性要求、測試用例說明、使用者界面設計文檔、模型、使用者個人資訊以及其它各種人工生成的資訊。一般來說簡單的應用程式隻有一個簡單的說明文檔。

  借助這些文檔,你可以快速确定需要測試的用例。每個測試用例都描述了應用程式可以執行的一項功能。用規模相近的測試方案确定唯一的功能是一個好習慣,而較大的方案可以根據其檢驗的功能拆分為較小的方案。

  有許多種建立用例模型的方法,其中最簡單的便是輸入/輸出比對法。在saxon的query類中,最簡單的用例是傳送一個查詢檔案、一個查詢請求和一個輸出檔案路徑。輸出檔案若不存在,将根據要求建立,并在檔案中顯示查詢結果。

   更複雜的用例可能需要輸入更多的資訊或輸出更多的結果。然而,用例并不關心功能是如何在内部實作的。對它們來說,軟體就像是一個 “黑盒子”,隻要運作正常,即使真正實作軟體功能的是盒子裡的侏儒也無所謂。這是很重要的一點,因為輸入/輸出比對用例很容易直接轉換為測試用例,使得複 雜的說明與簡單的測試吻合,确定該運作的功能正常運作,而不該運作的功能如預期一樣失效。

  如果類相對比較簡單,或者已有列舉類所有功能的說明文檔,為指定入口點描述用例将很容易。如果不是這樣,或許就需要研究類可能有的所有行為(确定類的目的與用法)。如果你想知道所有調用代碼的地方,也可以從代碼中提取用例。

  最可能的情況是,根據開發人員提供的類的一些基本說明文檔,可以完全确定這些類應有和不應有的行為。基于此,設計一套準确的用例集。

  轉換測試用例

  每個測試用例都由兩部分組成:輸入和預期輸出。輸入部分包括所有建立變量或為變量指派的測試用例語句。預期輸出部分則表明應該得到的輸出結果,它應該顯示斷言成立或“沒有異常”(不存在斷言語句時)這樣的資訊。

  基本的輸入/輸出模式是了解測試用例模型最簡單易用的辦法。它采用一般函數(傳遞參數,擷取傳回值)和大多數使用者行為(按某個鍵實作某項功能)慣用的模式。然後,可以用該模式進行:

  初始化:建立測試預設環境。代碼初始化可以在測試開始時進行或通過調用setup()方法實作。

  運作:調用被測試的代碼,記錄所有值得注意的輸出和資料。

  檢查:使用斷言語句確定代碼正常運作。

舉例來說,假設要測試saxon庫的轉換類入口點。其中一個用例是将xml檔案轉換為html檔案,當然前提是已有描述這個轉換的xsl檔案。輸入這三個檔案的路徑,就應該輸出html檔案的内容。這可以直接轉為下面的測試:

public void testxsltransformation() {

/* initialize the variables

(or do this in setup if used in many tests) */

string processmepath = "/path/to/file.xml";

string stylesheetpath = "/path/to/stylesheet.xsl";

string outputfilepath = "/path/to/output.xml";

//do the work

transform.main(new string[] {

processmepath,

stylesheetpath,

"-o", outputfilepath } );

//check the work

asserttrue(checkoutputfile(outputfilepath));

}

  每一步都可以根據需要進行增減。這裡聲明的變量也可以簡單地通過調用方法來指派。預期輸出的實作是由幾個步驟組成。如果成功得到預期輸出,有時可以省略檢查步驟。

  雖然這個模式簡單且靈活可變,但是第二步必不可少。這個模闆沒有告訴我們尋找要測試代碼的方法,也不能保證代碼以友善測試的方式運作。這是個需要認真考慮的問題。

  功能測試

  通過确定執行程式功能的主要代碼段,可以将測試建立在一個更有效的環境下。由于這些類提供了從系統外部進行測試的途徑,是以也是代碼的入口點。

  是以,功能測試的整體目标就是确定一組可以通路系統功能的高層接口類。這些類的獨立性越高越好。畢竟,如果能将類從環境中分離出來,測試起來會更加容易。

  确定作為入口點的代碼是一個簡單的過程。在代碼庫中,通常有幾個控制該庫所有功能的入口點。這些外部類作為用戶端代碼,與庫的中介對象将開發人員從複雜的代碼分析中解脫出來。這些便是應當首先對其方法進行測試的類。

  比如,saxon有一小組類作為邏輯入口點提供對庫的通路。通過對外部類進行編碼操作,比如轉換、設定和查詢,用戶端代碼可以通路庫的許多功能 類,而無需考慮類的接口問題,甚至無需擔心這些類是否存在。這些外部類用高層易用的接口提供一個簡單的方式對系統功能進行測試,這正是一個優良的庫的特 征。

  程式代碼中的各個功能子產品通常是各自獨立的。在某些代碼中,甚至可以認為這些子產品各自對應不同的、可通過大量外部類通路的庫。這些類查找高層接口的邏輯位置。插件結構通常都采用這種設計模式:每個插件程式都有一個可以有效執行内部代碼全部功能的簡單接口。

  在一些非嚴格描述的系統中,通常有一個所有程式行為的中介點。在mvc架構中,這個中介類一般作為“控制器”,負責配置系統各部分的請求路由。整體系統的功能主要由這個控制器連接配接的類實作,是以,這些類是測試的主要對象。

  比如在 applet程式設計中,java.applet.applet的派生類就是所有代碼的中心處理單元。根據代碼的分解程度,測試焦點可以放在applet 子類或與其連接配接的類上。

  連接配接各個子產品的代碼也是測試的主要對象。将應用程式請求轉換為資料庫查詢的類,以及有相似功能的适配類是其次應該考慮的測試對象。

  各種基于mvc(模式-視圖-控制器)架構的元件可以用其它的測試架構(比如junit的擴充)進行測試。例如,struts的 action類就最好使用junit的擴充strutstestcase進行測試;伺服器端的元件(如servlets、jsp和ejb)最好用 catus進行測試;而httpunit則是對web應用程式進行黑盒測試的最好架構。本文讨論的所有技術都可應用于這些架構環境下的測試。

  從用例到測試用例

  每個入口點都必須與相應的用例比對。某些情況下可以忽視這一步,因為類名的自記錄可以實作自動比對,比如 saxon中的轉換類可以實作xsl轉換,查詢類可以進行xquery轉換。

  其它情況則要複雜得多。通常用例描述的功能隻能以橫切關注點的方式存在,不能用任何單獨的類進行例證。隻有幾組類互動時或滿足一定條件時,才能觀察到功能行為。這種情況下,測試的初始化程式會比較長,或者可以用 setup()方法提供需要的測試環境。

  而調用代碼的運作程式應該盡可能地設計成一行,以減少與被測試代碼的關聯,這可以有效避免對邊緣效應與不穩定實作細節的依賴。測試的檢查階段是 最複雜的,因為這個階段經常需要添寫非測試用代碼。測試時可能需要對結果進行嚴格的分析以確定其符合要求。有時甚至需要将這個過程分為幾步來完成,以取得 測試可以識别的結果。在xsl轉換中,這兩種情況都是可能的,結果儲存在檔案中,然後以xml格式讀入記憶體并進行準确性分析。

  saxon中有個相對簡單的例子。已有xml檔案和xpath表達式的情況下,saxon可以執行表達式并傳回比對清單。saxon中的xpathexample樣本類就是用來執行這種任務的。基于以上分析,可以設計如下的測試流程:

public void testxpathevaluation() {

//initialize

xpathevaluator xpe = new xpathevaluator(

new saxsource(new inputsource("/path/to/file.xml")));

xpathexpression findline =

xpe.createexpression("/some/xpath[expression]");

//work

list matches = findline.evaluate();

//check

asserttrue(matches.count() > 0);

  兩次輸入的都是字元串常量,輸出的則是所比對的清單,可以用來驗證比對結果的正确性。這些工作都由一行代碼完成,這行代碼隻是簡單地調用了被測試的方法。

  另一種可能的情況是xpathevaluator沒有調用createexpression()方法。因為表達式不存在,這時可能會顯示錯誤資訊。

  将輸入的源檔案名和表達式保留在測試用例中不是個好習慣。某些項目(伺服器名、使用者名和密碼等)不應該出現在測試檔案中,它們應該可以根據情況 自由設定。并且,測試用例的設計應該友善測試驅動和測試資料的分離、測試驅動對大範圍資料的可重用性和測試資料對測試驅動的可重用性。另一方面,不要将一 個簡單的測試用例實作設計地過于複雜。一般來說,測試用例已經說明了系統的大部分狀态,并可對其進行參數描述,是以無需在測試中進行過于詳細的參數描述。

  許多代碼段可能出現在不止一個測試用例中。有經驗的面向對象開發人員會嘗試對其進行重構并建立通用類和有效方法。有時候這樣做非常有用,比如登入過程應該設計成所有測試用例可用的方法。 但是,不要過度設計測試,這些java類僅僅是用來驗證應用程式的功能行為而已。

  測試用例是脆弱的。比如,如果開發人員更改了testxpathevaluation測試中輸入檔案的位置,或者createxpression方法簽名有所變動,測試腳本就會失效。

  對于應用程式的測試用例實作來說,大量的重複性工作與改動是不可避免的。是以,可跟蹤性對于所有的測試用例都是至關緊要的。出現問題的時候,如果能為開發人員指出相應的測試用例說明和用例說明将有利于提高修正bug的速度。

  是以,測試用例注釋中應标明原始說明文檔的引用位置。這可以是一個簡單的代碼注釋,也可以對每條測試都注釋相關用例和所測功能,這樣當測試出現問題時開發人員就會收到一條相關資訊。是以,在代碼中加入參考并維護可追蹤性是很重要的。

  設計運作時事件表

  要了解測試覆寫的範圍,必須先了解所測試代碼如何運作,以及各種靜态類如何形成描述程式狀态的動态對象圖表。

  有許多模拟這種行為的方法,包括granovetter圖和物件互動圖。其基本思想是用圖形化的方式研究代碼以了解測試中涉及到的運作時部分。 這些技術都可用運作時事件表(runtime event diagrams)來描述,因為這些圖表顯示了程式運作時發生的事件,而非理論上類可以控制的事件。這些圖表非常重要的原因包括:

  首先,這些圖表便于從高層上了解代碼,并提供有用的說明文檔。這個文檔與代碼的内聯文檔不同。這些圖表顯示代碼的運作時表現,是産生代碼功能的地方,也易于對系統的了解;大多數設計模式和架構在用對象和參考表示時要比用類和域表示容易得多。

  另外,這些圖表将測試執行的代碼分類清單,并确定測試是否會受到将來對任意代碼改動的影響。如果開發人員确定測試a是建立在b、c和d的基礎上,她就可以确定如果對b、c或d做出改動就需要對a進行重新測試(確定向後相容)。

  以盡可能少的步驟模拟系統是個好方法。總的來說,實際調用與此無關,重要的是系統如何作為整體運作以獲得預期目标。可以用簡化的模拟系統實作這個目的,該系統隻關心對象間的基本互動,并用自然語言描述互動中發生的事件。

  做出運作時事件表後,就可以将其整合到類文檔中。需要注意的是,為表添加一些限制可使其對類的修改更有彈性。首先,一般不能使用方法名,因為它 們會随時間發生變化。取而代之的是更易了解的自然語言描述。其次,這些圖表主要是關于系統中各部分的互動。這是高層架構上的設計方案,一般不會再做改動。 最後,圖表是建立在類型而非特定類的基礎上。隻要基本類型不變,為維持互動協定的正常運作,這些圖表就不需要更新。

  一旦圖表建立成功,可以在許多方面獲得應用。比如,一個圖表可以用來擷取系統如何運作,以及如何運用其互動部件實作功能的概覽。在某種程度上這是一種簡化了的uml語言,它隻描述關系到整體功能的系統部件:執行個體及其類型、其它引用的執行個體,以及元件可以實作的功能。

  這些圖表也可以用來分析系統的複雜性以及如何進行簡化。要确定簡化系統的方法,可以查找系統中使用過一到兩次的對象,并為其尋找其它可能更合适的位置。也可以查找重複的任務,将其封裝到方法或類中。

  然而,最重要的是圖表在測試中的應用。通過對系統狀态的總結,圖表可以幫助解決系統中出現的問題。出現問題時,圖表中的資訊便可用作參考。因為 隻需要将系統目前狀态與預期狀态作比較即可,這樣确定問題産生的原因也就變得比較簡單了。對小元件的改動不應該影響整體架構,是以可以通過對照運作時事件 表以保證系統仍然正常運作。并且,當有重要元件發生變動時,可以用運作時事件表對照系統目前狀态以擷取系統修正方案。由于将系統作為整體和對預期功能的描 述,運作時事件表也可以看作是一種結構化的單元測試。如果系統有變動,可以更容易地做出修正以維持系統的正常功能。

  如果經常因細節問題影響對全局的把握,就應該使用圖表。其高層本質可以用來分析軟體的設計模式,就像反模式一樣。還有許多其它用途,并且當運作時事件表、測試用例說明和用例說明沒有描述所需的細節時,它還提供了直接進行代碼分析的路線圖。

  利用功能測試進行回歸測試

  最後,為回報你在功能測試上做出的努力,配置一個與自動生成的程式相應的自動化測試程式。這個程式不隻從功能上測試代碼,還可以同時進行正常的 回歸測試。現在大多開發項目都建立在龐大的代碼庫基礎上,如果不能對代碼庫進行充分測試,開發團隊将無從決定對程式的修正是否會破壞現有的功能,結果就是 很難對這種代碼進行擴充或優化。與此相反,如果開發人員可以在全面的功能測試基礎上進行回歸測試,優化或擴充代碼時就不必擔心可能會引發不可預料的問題。 畢竟,沒有比做完回歸測試後發現一切正常更令人心情愉快的事了。

本文出自seven的測試人生公衆号最新内容請見作者的github頁:http://qaseven.github.io/