2012-08-17 45 views
7

我在閱讀Clean Code: A Handbook of Agile Software Craftsmanship,其中一個示例涉及Portfolio類和TokyoStockExchange類。然而,Portfolio不是很好測試,因爲它取決於TokyoStockExchange作爲一個外部API來確定投資組合的價值,這是一個相當不穩定的查找,不利於測試。混淆爲什麼它對單元測試虛擬對象有用

因此,他們通過創建一個通用的StockExchange接口來解決這個問題,並且TokyoStockExchangeDummyStockExchange都實現了基類。因此,依賴倒置原則已經實現,並且在類別中可以實例化一個DummyStockExchange,將股票價格固定到公司,將該實例分配給該投資組合,並且將該公司的一些股票添加到該投資組合,然後斷言預期的價值確實是合適的價值。下面的代碼:

public class PortfolioTest 
{ 
    private DummyStockExchange exchange; 
    private Portfolio portfolio; 

    protected void setUp() 
    { 
     exchange = new DummyStockExchange(); 
     exchange.fix("MSFT", 100); 
     portfolio = new Portfolio(exchange); 
    } 

    public void GivenFiveMSFTTotalShouldBe500() 
    { 
     portfolio.add(5, "MSFT"); 
     Assert.assertEquals(500, portfolio.value()); 
    } 
} 

我的問題,簡單地說,就是爲什麼

我們試圖測試TokyoStockExchange類是否與Portfolio類一起工作。顯然,如果我們用一種設定股票價格的新方法創建另一個類別,然後給這些股票組合5個股票,那麼一切都將起作用。它似乎......無用測試。我知道TokyoStockExchange基本上不可能通過Portfolio進行測試,因爲股票價格不斷變化,但我不明白在相當無用的測試中如何消除這種情況。

這一切似乎不知道我們的加法器程序是否工作,但唯一可用的數字是隨機生成的,所以我們創建一個虛擬類,給我們一個2並測試如果2 + 2 = 4。好吧,顯然這是真的。我們仍然可以打破TokyoStockExchange,測試仍然會成功,因爲它正在測試另一個課程。如果有的話,這一切似乎都具有欺騙性,並且還導致不得不編寫額外的代碼來測試我們知道的工作。

我認爲這是我在理解單元測試時遇到的最大問題。我知道我錯了,我只是沒有看到我猜到的光。希望有人能幫助我。

+0

僅供參考......如果您使用*'Portfolio測試'TokyoStockExchange'工程*,那麼您超出了單元測試的範圍。 「投資組合」和「東京股票交易所」是單位,單元測試旨在從儘可能多的其他單位(最好全部)中分別測試每個單位*。它們之間的交互是*集成測試*涵蓋的內容。 – cHao 2012-08-17 02:21:14

+2

+1是爲了試圖理解價值,而不是用「維護代碼的更多工作」來吹掉單元測試。 – 2012-08-17 02:27:26

+0

這對於[程序員.se]來說更是一個問題。 – 2012-08-17 03:15:36

回答

7

這個想法是,你想要測試Portfolio類中的邏輯與TokyoStockExchange隔離。如果您使用像Moq或Rhino Mocks這樣的模擬框架,那麼您可以輕鬆地模擬來自TokyoStockExchange的不同輸出和行爲,並編寫單元測試以確保Portfolio正確響應。您將爲TokyoStockExchange課程編寫單獨的單元測試。

這並不是說你不需要整合這兩個類之間的測試。很難在不使用模擬對象的情況下正確驗證所有場景。

以這樣一個簡單的類作爲例子很難理解它的價值,但是給定一個更復雜的類,在那裏你需要驗證那些難以或不可能安排在「活」類的情況下的測試用例,單元測試變得更加重要。

+3

+1正確。您沒有測試TokyoStockExchange,您正在測試Portfolio類是否正常工作。 – CaffGeek 2012-08-17 02:19:52

+0

要添加:您想單獨測試,以便在測試失敗時更容易找到根本原因。如果你運行了20個不同類的集成測試,並且它「失敗」,你可能花費大量時間查看20個類來查找根本原因。如果您將一堆(如19)剔除並將粒度縮減到一個等級,則可大幅減少必須搜索/修復的表面積。 – 2012-08-17 02:21:38

+0

另外,通過創建一個絕對已知的**依賴關係**,可以從測試問題中刪除**可變性**。這創建了一個基準,您可以從中指出「在給定標準或預期輸入時,此對象與預期完全相同」。相反,請考慮依賴關係創建數據庫連接,但由於某種原因該連接失敗......您不希望此測試失敗,因爲這不代表您正在測試的內容。 – 2012-08-17 02:25:09

3

有兩種測試你應該做,單元測試和集成測試。

單元測試應該是一個白盒測試,您可以單獨測試每個代碼單元。通常這是指您的每個類中的公共接口。你嘲笑他們的依賴關係,這樣你就可以確信,給定一組已知的數據,你的部門將返回可預測的結果。

你說,「顯然一切都會工作」在單元測試。這假設您的代碼中沒有任何錯誤。如果你能做出這樣的假設,那麼你就不需要首先測試任何東西!而且你並不需要單元測試所有的東西---如果你的Portfolio只是你的StockExchange的一個薄層,它調用API方法並傳遞結果,那麼你不應該費心單元測試它。另一方面,如果您的Portfolio具有真正的邏輯,您可能需要對其進行單元測試。假設Portfolio有一種方法可以從Stock Exchange中提取數據,分析數據,並在股票價格出現異常時向用戶發送警報消息,例如價格開始快速下跌。您希望確保警報實際上將在預期條件下發送,但您不想坐下來等待下一次股市崩潰。所以在你的單元測試中,你會模擬Stock Exchange產生你想要觸發警報的值的種類,然後檢查它是否真的發生了。如果確實如此,那麼很好,如果不是的話,你只是發現了一個bug。

集成測試將同時測試這兩個單元,也很重要。但是在集成測試中模擬某些類型的場景更加困難,並且在確定錯誤實際隱藏的位置時幫助不大。如果您爲應用程序運行集成測試並發現應用程序沒有發送警報,那麼問題出在哪裏?第三方API中是否存在錯誤?證券交易所是否向你發送了不良的價值?您的警報系統是否將郵件發送到錯誤的地址?您可能需要一段時間才能發現分析方法存在問題。