2013-08-26 232 views
4

我已經繼承了一個沒有接口或抽象類的項目,即具體的類,我想引入單元測試。這些類包含很多函數,其中包含業務邏輯和數據邏輯;打破了SOLID的每個規則(http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29)。單元測試具體類

我有一個想法。我正在考慮爲每個設計不佳的類創建接口,從而暴露出所有功能。那麼至少我可以模擬這些課程。

我對單元測試相對較新(我有一個項目的經驗,這個項目在正確的地方使用界面非常完善)。這是否是一個好主意,即爲所有具體的類創建接口(公開所有函數和子例程),僅用於單元測試?

我花了一些時間研究這個,但我還沒有找到答案。

回答

1

是的,這是一個良好的開端,但是,其接口比有依賴注入少一個優先的。如果你所有的遺留類都獲得了接口,但是在內部隱藏,它們仍然是相互依賴的,這些類仍然不容易測試。舉例來說,假設你有一個看起來像這樣兩類:

Public Class LegacyDataAccess 
    Public Function GetAllSales() As List(Of SaleDto) 
     ' Do work with takes a long time to run against real DB 
    End Function 
End Class 

Public Class LegacyBusiness 
    Public Function GetTotalSales() As Integer 
     Dim dataAccess As New LegacyDataAccess() 
     Dim sales As List(Of SaleDto) = dataAccess.GetAllSales() 
     ' Calculate total sales 
    End Function 
End Class 

我知道你已經說... 「我希望遺留代碼至少是分層的那麼好」,但讓用它作爲一些遺傳代碼的例子,這些遺傳代碼很難測試。難以測試的原因是因爲代碼伸出數據庫並對數據庫執行耗時的查詢,然後從中計算結果。因此,爲了以當前狀態測試它,您需要先將大量測試數據寫入數據庫,然後運行代碼以查看它是否基於插入的數據返回正確的結果。不得不寫一個這樣的測試是有問題的,因爲:

  • 這是一個痛苦的代碼寫入到安裝測試
  • 測試將是脆弱,因爲它取決於正常工作外數據庫和它含所有正確的支持數據
  • 測試將需要很長時間才能運行

正如你正確地觀察,接口單元測試非常重要。因此,當你建議,讓我們來添加界面,看看它使任何更容易測試:

Public Interface ILegacyDataAccess 
    Function GetAllSales() As List(Of SaleDto) 
End Interface 

Public Interface ILegacyBusiness 
    Function GetTotalSales() As Integer 
End Interface 

Public Class LegacyDataAccess 
    Implements ILegacyDataAccess 

    Public Function GetAllSales() As List(Of SaleDto) _ 
      Implements ILegacyDataAccess.GetAllSales 
     ' Do work with takes a long time to run against real DB 
    End Function 
End Class 

Public Class LegacyBusiness 
    Implements ILegacyBusiness 

    Public Function GetTotalSales() As Integer _ 
      Implements ILegacyBusiness.GetTotalSales 
     Dim dataAccess As New LegacyDataAccess() 
     Dim sales As List(Of SaleDto) = dataAccess.GetAllSales() 
     ' Calculate total sales 
    End Function 
End Class 

所以現在我們有接口,但實際上,如何使它變得更容易測試?現在我們可以輕鬆創建一個模擬數據訪問對象,它實現相同的接口,但這不是真正的核心問題。問題是,我們如何讓業務對象使用模擬數據訪問對象而不是真實的?要做到這一點,你需要通過引入依賴注入來將你的重構提升到下一個層次。真正的罪魁禍首是New關鍵字在以下行業務類:

Dim dataAccess As New LegacyDataAccess() 

商務艙清楚地依賴於數據訪問類,但目前它是隱藏這一事實。這是說謊的依賴關係。這就是說,來吧,很簡單,只需調用這個方法,我就會返回結果 - 這就是所需要的。當真的時,它需要更多的東西。現在,讓我們說,我們從臥談它的依賴停止了它,並使它所以它毫不掩飾地說他們,是這樣的:

Public Class LegacyBusiness 
    Implements ILegacyBusiness 

    Public Sub New(dataAccess As ILegacyDataAccess) 
     _dataAccess = dataAccess 
    End Sub 

    Private _dataAccess As ILegacyDataAccess 

    Public Function GetTotalSales() As Integer _ 
      Implements ILegacyBusiness.GetTotalSales 
     Dim sales As List(Of SaleDto) = _dataAccess.GetAllSales() 
     ' Calculate total sales 
    End Function 
End Class 

現在,你可以看到,這個類是容易測試。我們不僅可以輕鬆創建模擬數據訪問對象,現在我們可以輕鬆地將模擬數據訪問對象注入到業務對象中。現在我們可以創建一個模擬,它可以快速而輕鬆地返回我們希望返回的數據,然後查看業務類是否返回正確的計算 - 不涉及數據庫。

不幸的是,雖然向現有類添加接口非常簡單,但重構它們以使用依賴注入通常需要更多的工作。您可能需要計劃出哪些類最適合首先處理。您可能需要創建一些中介的舊式封裝,這些封裝符合代碼的習慣,因此您在重構代碼的過程中不會破壞現有代碼。這並不是一件快速簡單的事情,但如果你耐心並且長期堅持下去,就有可能做到這一點,你會很高興你做到了。

+1

感謝DI提醒。 +1。 – w0051977

+1

並提供全面的解答。 – w0051977

+1

謝謝。我將不勝感激,如果你會看看我的數據倉庫/ VB.NET問題在這裏:http://stackoverflow.com/questions/18495622/data-warehouse-type-solution#18495622。你提供了設計模式類型問題的好答案,所以我想我會問。 – w0051977

1

我會建議你去interface路線,但如果你想支付的解決方案,然後嘗試其中之一:

+0

付費路線+1。你知道任何可以用來模擬具體課程的免費工具嗎?如果需要,我相信我的公司會支付。 – w0051977

+0

我認爲'微軟鼴鼠'和'微軟假貨'做到了,但不確定。 「Microsoft Fakes」也不是真正免費的,因爲你必須擁有Visual Studio的Ultimate版才能獲得它。 「微軟鼴鼠」可能值得一看。 –

0

創建界面測試這些類不是一個壞主意 - 單元測試的目標是在一個類的函數按照預期運行的情況下運行。根據你正在使用的類,這可能比說起來容易 - 如果對全局狀態有很多依賴,你將需要相應地進行嘲諷。

考慮到單元測試的重要性,將一些工作放到其中(限制)會使您和您使用的開發人員受益。

3

這是一個難以解決的問題。我認爲你在正確的軌道上。你最終會得到一些難看的代碼(比如爲每個單一類創建header interfaces),但這應該只是一箇中間步驟。

我建議投資Working Effectively with Legacy Code的副本。首先,您可以先閱讀this distillation

除了Karl的選項(可讓您通過攔截模擬)之外,您還可以使用Microsoft Fakes & Stubs。但這些工具不會鼓勵您重構代碼以遵守SOLID原則。

+0

爲書鏈接+1。 – w0051977

5

如果您的項目完全沒有測試,在添加任何單元測試之前,我寧願創建更高級別的測試(即驗收,功能和/或集成測試)。

當您準備好這些測試,你知道,該系統表現,因爲它應該而且也在於它具有「外部」質量一定水平(這個意思是你的程序的輸入和輸出所預計的) 。

一旦您的高級別測試正在運行,您可以嘗試將單元測試添加到已存在的類中。

我敢打賭,你會發現自己在需要重構現有的一些類的,如果你希望能夠進行單元測試他們,你可以用你的較高水平測試作爲一個安全網,將告訴你如果破壞了任何東西。

+0

最後一段爲+1。我正在使用測試驅動開發重構代碼基礎。是否有任何測試工具可以推薦用於集成測試?功能測試或驗收測試? – w0051977

+0

對於需要查看幾個類一起工作的集成測試,您可以使用其中一個xUnit庫。對於更高級別的測試,它將取決於您的程序的界面:看看諸如Selenium Webdriver,Fitnesse等... – jsanchez

+0

我非常同意高級別測試應該是此處的首要任務。修正錯誤並能夠斷言系統正常工作通常比改進代碼設計更緊迫。我建議堅持一個xUnit框架,只是測試你可以用這個框架實現的最高級別。 –

0

我更喜歡創建接口和類,因爲你需要測試的東西,而不是所有的前期。

除了接口,您可以使用一些技術來測試遺留代碼。我經常使用的是「Extract and Override」,在這裏你可以從其他方法中提取一些「不可測試」的代碼片斷,並使其覆蓋。他們派生你想要測試的類,並用一些傳感代碼覆蓋「不可測試」的方法。

使用模擬框架就像將關鍵字Overridable添加到方法一樣簡單,並使用模擬框架設置返回。

你可以在「Working Effectively with Legacy Code」這本書上找到很多技巧。

有關現有代碼的一件事情是,有時候最好編寫集成測試而不是單元測試。在測試完你的行爲後,你可以創建單元測試。

另一個技巧是從模塊/類開始的依賴性較少,這樣,您就可以更輕鬆地熟悉代碼。

讓我知道,如果你需要有關「提取物和覆蓋」的例子。)