2009-12-04 38 views
2

我們剛剛發佈了一個爲我們專有系統重寫的模塊(第三次)。這個模塊,我們稱之爲負載管理器,迄今爲止是迄今爲止我們系統中所有模塊中最複雜的。我們試圖獲得一個全面的測試套件,因爲每當我們對這個模塊做出任何重大改變時,都會花費數週時間來解決錯誤和怪癖。然而,開發一個測試套件已經證明是相當困難的,所以我們正在尋找想法。需要TDD方法的想法

負載管理器的內存駐留在一個名爲LoadManagerHandler的類中,這實質上是模塊背後的所有邏輯。該處理程序調用多個控制器在數據庫中執行CRUD方法。這些控制器實際上是位於頂部的DAL的頂層,並將我們的LLBLGen生成的代碼抽象出來。

所以很容易模擬這些控制器,我們正在使用Moq框架。然而問題出在Load Manager的複雜性上,我們收到的問題並不是處理簡單的案例,而是處理器中包含大量數據的情況。

簡要解釋負載管理器包含一些「卸載」細節,有時數百個,然後放入用戶創建的負載和reship池中。在創建和填充這些負載的過程中,會有大量的刪除,更改和添加,最終導致問題出現。然而,因爲當你嘲笑對象的方法,最後模擬勝,即:

jobDetailControllerMock.Setup(mock => mock.GetById(1)).Returns(jobDetail1); 
jobDetailControllerMock.Setup(mock => mock.GetById(2)).Returns(jobDetail2); 
jobDetailControllerMock.Setup(mock => mock.GetById(3)).Returns(jobDetail3); 

不管我送jobDetailController.GetById(X)我將永遠找回jobDetail3。這使得測試幾乎不可能,因爲我們必須確保在更改所有點都受到影響時應該受到影響。

因此,我決定使用測試數據庫,只是允許正常的讀寫操作。但是,因爲不能(讀取:不應該)指定測試的順序,所以早期運行的測試可能會導致稍後運行的測試失敗。

TL/DR:我本質上是在尋找面向數據的代碼的測試策略,這種代碼本質上是非常複雜的。

回答

1

要解決「最後模擬勝」與起訂量,你可以從這個博客使用的技術:

Moq Triqs - Successive Expectations

編輯:

其實你甚至不需要說。根據你的例子,Moq將根據方法參數返回不同的值。

public interface IController { string GetById(int id); }

class Program 
{ 
    static void Main(string[] args) 
    { 
     var mockController = new Mock<IController>(); 

     mockController.Setup(x => x.GetById(1)).Returns("one"); 
     mockController.Setup(x => x.GetById(2)).Returns("two"); 
     mockController.Setup(x => x.GetById(3)).Returns("three"); 

     IController controller = mockController.Object; 

     Console.WriteLine(controller.GetById(1)); 
     Console.WriteLine(controller.GetById(3)); 
     Console.WriteLine(controller.GetById(2)); 
     Console.WriteLine(controller.GetById(3)); 
     Console.WriteLine(controller.GetById(99) == null); 
    } 
} 

輸出是:

 
    one 
    three 
    two 
    three 
    True 
+0

我提高了你的答案,因爲我不知道這是可能的,但我不相信這將完全達到我期待的結果。我需要根據所使用的參數返回特定的數據,並且此方法不一定會按預定順序調用。不過非常感謝,這會幫助我在其他地方。 – joshlrogers 2009-12-04 17:23:39

+0

好的....有什麼想法,爲什麼這不適合我呢?我正在使用Moq的最新版本,無論我傳入什麼參數,我都會收到最後的模擬返回結果。 – joshlrogers 2009-12-04 17:30:16

+0

@josh - 不知道。我使用v3.1.416.3進行了測試,該版本是該網站的最新非beta版本。這是標準行爲,這就是爲什麼必要時引入It.IsAny語法來覆蓋它的原因。我建議你用控制器編寫最簡單的測試,看看你能否消除變量。您也可以發表Moq討論 - 他們非常有幫助。 http://groups.google.com/group/moqdisc – TrueWill 2009-12-04 17:36:43

0

我從來沒有使用Moq,但它似乎應該能夠通過提供的參數匹配模擬調用。

就讓我們來看看在Quick Start documentation有以下摘錄:

//Matching Arguments 

// any value 
mock.Setup(foo => foo.Execute(It.IsAny<string>())).Returns(true); 


// matching Func<int>, lazy evaluated 
mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true); 


// matching ranges 
mock.Setup(foo => foo.Add(It.IsInRange<int>(0, 10, Range.Inclusive))).Returns(true); 

我想你應該可以使用上面的第二個例子。

+0

感謝您的回覆。我嘗試了多種不同的方式,但最終這是我發現的(在備註部分): http://www.clariusconsulting.net/labs/moq/html/3BFDD309.htm – joshlrogers 2009-12-04 17:15:27

+0

@josh - 哇,那鏈接的誤導。我打算就此向作者發送反饋意見。 – TrueWill 2009-12-04 17:39:54

+0

@josh - 我認爲你所看到的鏈接確實是說「如果有兩個期望與最後一個贏得的結果相匹配」。 就你而言,如果你使用It.Is匹配器,那麼就不會有衝突。 – 2009-12-04 19:46:52

0

一個簡單的測試技術是確保每次一個錯誤記錄對一個系統中,確保一個單元測試寫入覆蓋這種情況下。你可以用這種技術建立一套非常堅實的測試。甚至更好,你不會兩次碰到同一件事。

0

不管我送jobDetailController.GetById(X)我將永遠找回jobDetail3

你應該花更多的時間調試測試,因爲正在發生的事情並不怎麼起訂量行爲方式。您的代碼或測試中存在導致某些操作不當的錯誤。

如果你想用相同的輸入但不同的輸出重複調用,你也可以使用不同的模擬框架。 RhinoMocks支持錄製/播放習慣用法。你是對的,這並不總是你想要強制執行呼叫順序。我更喜歡Moq,因爲它很簡單。

2

正如勒布指出的,你的確可以使用一系列配套:

controller.Setup(x => x.GetById(It.IsInRange<int>(1, 3, Range.Inclusive))))).Returns<int>(i => jobs[i]); 

該代碼使用傳遞給方法來計算要返回的值的論點。

1

這聽起來像LoaderManagerHandler做...很多工作。在課堂名稱中的「經理」總是有點擔心我......從TDD的角度來看,如果可能的話,可能需要考慮如何適當地打破課堂。

這個班多久了?

+0

LoadManagerHandler確實做了很多工作,它現在包含了實際Load Manager模塊的所有邏輯(模塊的名稱是「manager」包含在類名稱中的唯一原因)。創建這個類是爲了將這個模塊的整個邏輯包含在一個位置中而嘗試刪除。它大約是1400行代碼,所以不是一個龐然大物,很多是參數驗證等。所有的數據訪問代碼都是由數據層處理的。從我讀過的內容來看,這應該是TDD的最佳方式,但是我可能會誤會。 – joshlrogers 2009-12-14 14:56:44

+0

就我個人而言,我認爲1400行是一個非常大的類,可能需要重構爲較小的大小,儘管不是病態的巨大。除了規模之外,對班級的描述聽起來像是有許多責任,違反了「單一責任原則」併成爲上帝階級。無論如何,當我聽到「課堂太複雜,有太多依賴要測試」時,我的直接反應總是「把它分解成更小,更簡單的課程」。控制模式的反轉可能會有所幫助。 – kyoryu 2009-12-15 21:32:38