元件的測試過程包括將輸入傳送至介面、等待由元件來處理,然後檢查結果。在處理過程中,元件很可能會傳送輸入給其他元件,並採納其處理結果,藉此運用其他元件:
圖 1:測試您已實作的元件
其他元件可能導致測試發生問題:
-
尚未實作。
-
有瑕庛,導致測試無法執行,或浪費您許多時間,最後才發現測試失敗不是由您的元件造成。
-
造成您需要測試時難以執行測試。如果元件是商用資料庫,則表示您的公司可能沒有足夠的浮動式授權可分配給每個人。或者,其中一個元件可能是只有在預訂時間和獨立實驗室中才能使用的硬體。
-
造成測試速度過慢,以至於測試不足。例如,每一次測試時,光是起始設定資料庫就需要 5 分鐘。
-
很難引出元件產生特定的結果。例如,您可能希望每一個寫入磁碟的方法可以處理「磁碟已滿」的錯誤。您要如何確保呼叫此方法時就剛好填滿磁碟呢?
為了避免這些問題,您可以選擇使用 Stub 元件(又稱為模擬物件)。以您的元件在回應測試時傳給 Stub 的值來看,至少就此而言,Stub
元件就像真實的元件一樣。也許還不僅止於此:可能做為一般用途的模擬器,試著準確模仿元件的多數或全部的行為。例如,建置硬體的軟體模擬器,通常就是不錯的策略。除了速度稍慢以外,實際表現就像硬體一樣。好用的原因在於有更好的除錯支援、有更多可用的複本,且在硬體完工之前就可以使用。
圖 2:排除相依元件來測試您已實作的元件
Stub 有兩項缺點。
-
建置成本高(尤其是模擬器)。由於是軟體,所以也需要維護。
-
可能隱匿錯誤。例如,假設您的元件用到三角函數,但目前尚無任何程式庫。您的三個測試案例要求三個角度的正弦:10 度、45 度、90 度。您先以計算機算出正確的值,然後為正弦建構一個 Stub,分別傳回
0.173648178、0.707106781 及 1.0。一切正常,直到整合元件和實際的三角程式庫時,就出現問題,因為三角函數的正弦取用 radians 中的引數,所以傳回
-0.544021111、0.850903525 及 0.893996664。這是您稍後在程式碼中會發現的問題,且浪費太多時間,實在不值得。
除非建構 Stub 是因為實際元件尚未完成,否則 Stub 應該會一直保留到部署之後。Stub 支援的測試在產品維護期間可能很重要。因此,Stub 撰寫的標準必須高於用完即丟的程式碼。雖然不必達到產品程式碼的標準 -
例如,通常不必有自己的測試套組 - 但後續的開發人員將必須隨著產品元件改變來維護 Stub。如果維護太困難,Stub 終究難逃被捨棄的命運,投資也會化為烏有。
尤其在打算保留 Stub 時,Stub 會改變元件的設計。例如,假設您的元件會以資料庫來持續儲存鍵值配對。在此以兩個設計範例情節為例:
範例情節 1:資料庫用於測試和正式用途。不必隱藏資料庫,可開放給元件使用。您可能以資料庫名稱來起始設定:
public Component(
String databaseURL) { try { databaseConnection = DriverManager.getConnection(databaseURL); ... } catch (SQLException e) {...} }
此外,您不希望讀寫每一個位置的值來建構 SQL 陳述式,您一定有一些包含 SQL 的方法。例如,需要一個值的元件碼可能會呼叫下列元件方法:
public String get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery(
"SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} }
範例情節 2:基於測試的目的,以 Stub 取代資料庫。元件碼同樣也要注意是針對真實的資料庫或 Stub 來執行。因此,必須撰寫成使用抽象介面的方法:
interface KeyValuePairs { String
get(String key); void
put(String key, String value); }
測試將以類似雜湊表這種簡單的機制來實作 KeyValuePairs:
class FakeDatabase implements KeyValuePairs { Hashtable table = new Hashtable(); public String
get(String key) { return (String) table.get(key); } public void
put(String key, String value) { table.put(key, value); } }
不在測試中使用時,元件會利用配接器物件,將 KeyValuePairs 介面的呼叫轉換成 SQL 陳述式:
class DatabaseAdapter implements KeyValuePairs { private Connection databaseConnection; public DatabaseAdapter(String databaseURL) { try { databaseConnection = DriverManager.getConnection(databaseURL); ... } catch (SQLException e) {...} } public String
get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} } public void
put(String key, String value) { ... } }
您的元件可能只有一個建構子用於測試及其他用戶端。此建構子會取用一個實作 KeyValuePairs. 或者,也可能只為了測試而提供此介面,並要求元件的一般用戶端必須傳入資料庫的名稱:
class Component {
public Component(String databaseURL) { this.valueStash = new DatabaseAdapter(databaseURL); } // For testing.
protected Component(KeyValuePairs valueStash) { this.valueStash = valueStash; } }
因此,就用戶端程式設計師的觀點而言,這兩種設計範例情節會產生相同的 API,只是其中一個比較容易測試 (請注意,有些測試可能使用真實的資料庫,有些可能使用 Stub 資料庫)。
如需 Stub 的進一步相關資訊,請參閱:
|