2008-09-23 81 views
86

爲了幫助我的團隊編寫可測試的代碼,我想出了這個簡單的讓我們的C#代碼庫更具可測試性的最佳實踐列表。 (其中一些觀點指的是Rhino Mocks的限制,這是C#的嘲諷框架,但規則也可能更普遍適用。)有沒有人有他們遵循的最佳實踐?使用C#和RhinoMocks進行測試驅動開發的最佳實踐

爲了最大限度地提高代碼的可測試性,遵循下列規則:

  1. 先寫測試,然後代碼。原因:這可確保您編寫可測試的代碼,並確保每行代碼都爲其編寫測試。

  2. 使用依賴注入的設計類。原因:你不能模擬或測試無法看到的東西。

  3. 使用Model-View-Controller或Model-View-Presenter從其行爲中分離出UI代碼。原因:允許在無法測試的部分(UI)最小化時測試業務邏輯。

  4. 不要編寫靜態方法或類。原因:靜態方法很難或無法分離,Rhino Mocks無法嘲笑它們。

  5. 編程接口,而不是類。原因:使用接口可以闡明對象之間的關係。一個接口應該定義一個對象在其環境中需要的服務。另外,使用Rhino Mocks和其他嘲諷框架可以輕鬆模擬界面。

  6. 隔離外部依賴關係。原因:無法測試未解析的外部依賴關係。

  7. 將虛擬標記爲您打算模擬的方法。原因:Rhino Mocks無法模擬非虛擬方法。

+0

這是一個有用的列表。我們目前正在使用NUnit和Rhino.Mocks,對於不太熟悉單元測試這一方面的團隊成員來說,明確這些標準是很好的。 – 2008-09-24 13:38:02

回答

56

絕對是一個很好的清單。這裏有幾點想法:

先寫測試,然後是代碼。

我同意,在高層次。但是,我會更具體一些:「先寫一個測試,然後寫出只需要代碼來通過測試,然後重複。」否則,我會擔心我的單元測試看起來更像集成或驗收測試。

使用依賴注入的設計類。

同意。當一個對象創建它自己的依賴關係時,你無法控制它們。反轉控制/依賴注入爲您提供了該控件,允許您使用mocks/stubs /等隔離被測對象。這就是你如何獨立地測試對象。

使用Model-View-Controller或Model-View-Presenter從其行爲中分離出UI代碼。

同意。請注意,即使是演示者/控制器也可以使用DI/IoC進行測試,方法是將其呈現爲殘留/模擬視圖和模型。查看Presenter First TDD瞭解更多。

不要編寫靜態方法或類。

不確定我是否同意這一點。可以在不使用mock的情況下對靜態方法/類進行單元測試。所以,也許這是你提到的那些Rhino Mock特定規則之一。

編程接口,而不是類。

我同意,但原因稍有不同。接口爲軟件開發人員提供了極大的靈活性 - 除了支持各種模擬對象框架之外。例如,無法在沒有接口的情況下正確支持DI。

隔離外部依賴關係。

同意。使用界面隱藏外部依賴關係(如適用)。這將允許您將軟件與外部依賴關係隔離,無論是Web服務,隊列,數據庫還是其他內容。這是,尤其是當你的團隊不控制依賴(a.k.a. external)時很重要。

標記爲您想要模擬的方法。

這是Rhino Mocks的侷限性。在一個喜歡手工編碼存根的模擬對象框架環境中,這不是必需的。

而且,一對夫婦的新需要考慮的要點:

使用造物設計模式。這將有助於DI,但它也允許您隔離該代碼並獨立於其他邏輯進行測試。

使用Bill Wake's Arrange/Act/Assert technique編寫測試。這項技術非常清楚需要什麼配置,實際測試的內容以及期望的內容。

不要害怕推出自己的嘲笑/存根。通常,您會發現使用模擬對象框架會讓您的測試難以理解。通過滾動你自己,你可以完全控制你的模擬/存根,並且你將能夠保持你的測試可讀性。 (請參閱前一點。)

避免將單元測試中的重複重構爲抽象基類或設置/拆卸方法的誘惑。這樣做會隱藏來自開發人員的配置/清理代碼,以嘗試單元測試。在這種情況下,每個單獨測試的清晰度比重構重複更重要。

實施持續集成。在每個「綠色欄」上籤入您的代碼。構建您的軟件並在每次簽到時運行全套單元測試。 (當然,這本身並不是一種編碼習慣,但它是保持軟件清潔和完全集成的一個令人難以置信的工具。)

+2

我通常會發現,如果測試很難讀取,它不是框架的錯誤,而是它正在測試的代碼的錯誤。如果SUT設置起來很複雜,那麼也許它應該被分解成更多的概念。 – 2009-09-06 19:42:58

1

好的清單。你可能想要建立的東西之一 - 我不能給你太多的建議,因爲我自己開始考慮 - 當一個類應該位於不同的庫,名稱空間,嵌套的命名空間中時。您甚至可能希望事先弄清楚庫和名稱空間的列表,並要求團隊必須見面並決定合併兩個/添加一個新的。

哦,只是想到我做的事情,你可能也想。我通常有一個單元測試庫,每個類測試都有一個測試裝置,每個測試進入一個相應的命名空間。我還傾向於有另一個測試庫(集成測試?),它更多地位於BDD style。這使我可以編寫測試來指出該方法應該做什麼以及應用程序應該做什麼。

+0

我也在個人項目中做了類似的BDD風格測試部分(除了單元測試代碼)。 – 2008-09-24 00:11:03

10

如果您正在使用.Net 3.5,您可能需要查看Moq模擬庫 - 它使用表達式樹和lambda表達式去除大多數其他模擬庫的非直觀的記錄 - 回覆成語。

看看這個quickstart看到測試用例的更加直觀而成,這裏是一個簡單的例子:

// ShouldExpectMethodCallWithVariable 
int value = 5; 
var mock = new Mock<IFoo>(); 

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2); 

Assert.AreEqual(value * 2, mock.Object.Duplicate(value)); 
+5

我認爲Rhino Mocks的新版本也可以這樣工作 – 2008-09-24 13:21:06

0

這裏有一個另外一個,我想到的是我喜歡做的事。

如果計劃運行從TestDriven.Net或惡性不是從單元測試GUI測試,然後我發現它更容易設置的單元測試項目類型的控制檯應用程序,而不是庫。這使您可以手動運行測試並在調試模式下執行測試(前面提到的TestDriven.Net實際上可以爲您執行測試)。

另外,我總是喜歡開放一個Playground項目來測試我不熟悉的代碼和想法。這不應該檢查到源代碼管理。更好的是,它應該只在開發人員的機器上的單獨的源代碼控制庫中。

3

這是一個非常有幫助的職位!

我想補充一點,理解上下文和被測系統(SUT)總是很重要的。當您在現有代碼遵循相同主體的環境中編寫新代碼時,TDD主體對信件的處理要容易得多。但是,當您在非TDD傳統環境中編寫新代碼時,您發現TDD的努力可能會迅速超出您的估計和期望。

對於你們中的一些,誰住在一個完全學術界,時限和交付也許並不重要,但在軟件就是金錢,有效地利用你的TDD工作的環境是至關重要的。

TDD高度服從於Diminishing Marginal Return的法律。簡而言之,您在TDD方面的努力越來越有價值,直到您達到最高回報率,之後,隨後投入TDD的時間越來越少。

我傾向於認爲TDD的主要價值在於邊界(黑匣子)以及臨時白盒測試系統的任務關鍵區域。

2

接口編程的真正原因並不是爲了讓Rhino的生活更輕鬆,而是爲了闡明代碼中對象之間的關係。一個接口應該定義一個對象在其環境中需要的服務。一個類提供了該服務的特定實現。閱讀Rebecca Wirfs-Brock關於角色,責任和合作者的「對象設計」一書。

+0

同意...我將更新我的問題以反映這一點。 – 2009-09-09 23:29:56