單元測試的性能
單元測試必須運行得非???。如果單元測試組運行得太久,開發者就會停止運行它們,然后我們需要它們一直在運行。事實上,如果測試運行的時間超過了0.1秒,測試可能太慢了?,F在,如果你在過去運行過單元測試,你知道任何單元測試(要求訪問一個現場數據庫)運行的時間會更久。有NUnit時,你可以把測試放在categories里,每次運行測試的不同組時就會更加容易,同時大部分時間就會用在排除測試(此測試與數據庫連接)。但是至少,這些“慢”測試應該在Continuous Integration環境里每晚運行。這里有一個已經分類的單元測試的例子。
[TestFixture] [Category("Database Tests")] public class SomeTests { [Test] public void TestSomethingThatDependsOnDb() { ... } }
領域層單元測試
為了簡單化,這個應用程序的領域層很簡單,至少可以這樣說。但是即使在最簡單的領域層,在最小程度上,擁有每一個非異常途徑(被領域層覆蓋)。在命名空間BasicSample.Tests.Domain里有領域層測試。(一方面,CustomerTests.CanCreateCustomer測試每一個屬性的getter/setter時,它是不是非常具有殺傷力?在這個過程中你抓住了幾個瑣碎的bug)。在閱讀這篇文章時,你可能會注意到類型DomainObjectIdSetter.cs;下面將討論建立這個類型的動機。
為了運行單元測試,打開NUnit,轉到File/Open Project,打開BasicSample.Tests/bin/Debug/BasicSample.Tests.dll.為了阻止消耗時間的測試繼續運行,轉到NUnit的Categories t鍵,雙擊“數據庫測試”和"Web Smoke Tests."此外,點擊底部的“刪除這些類型”?,F在,當你運行單元測試時,只有域名邏輯測試將運行,而且不會被HTTP和數據庫訪問測試減速。對于這樣一個小的應用程序,補充的頂端的“慢”測試是可以忽略的,但是要增加時間來運行更大的應用程序的單元測試。
為數據訪問層使用Test Doubles 。
在進入模擬數據庫層之前,要注意這里有一個命名法來描述模擬服務的不同類型。Dummies,fakes, stubs and mocks都用來描述模擬行為的不同類型。對這些區別的總看法引起了一些東西,這些東西將包含在Gerard Meszaros' 即將出版的XUnit Test Patterns里。Meszaros提供了“雙重測試”,一般用來描述任何這些行為。Stubs和mocks就是例子代碼里顯示的這樣兩個雙重測試。
除非你明確測試DAO類別,通常你不需要運行單元測試,這些單元測試依靠現場數據庫。本質上,它們非常慢,而且不穩定。例如:如果數據改變了,測試就崩潰了。當測試域名邏輯時,如果數據庫改變,單元測試就不會崩潰。但是主要的障礙就是域名對象它們自己可能依靠DAOs。使用抽象的工廠模式(在例子中存在(以后將討論))和連接的DAO接口,我們能把DAO雙重測試注入到域名對象中,以此來模擬與數據庫通信。在CustomerTests.CanGetOrdersOrderedOnDateUsingStubbedDao中包括了一個例子。下面的片段,從單元測試創建了DAO stub,并且通過公共的安裝員,把它注入到customer中。因為安裝員僅僅希望執行IOrderDao接口,stub DAO就會簡單地代替所有現場數據庫行為。
Customer customer = new Customer("Acme Anvils"); customer.ID = "ACME"; customer.OrderDao = new OrderDaoStub();
編寫stub DAO 的另一個選擇,可以靜態地大量生成“無需實現接口”的代碼,而這些可以通過如Rhino Mocks 或NMock工具模擬DAO。無論哪個都是非常好的選擇,但是Rhino Mocks 以一種強而有力的方式調用方法,而不是使用數字符,而NMock 就是使用數字符。這使得能檢查它的使用編譯時間,協助重命名屬性和方法。演示示例CustomerTests.CanGetOrdersOrderedOnDateUsingMockedDao 告訴人們使用Rhino Mocks 3.0來創建模擬的IOrderDao 。盡管看起來建立一個模擬的對象比建立一個stub更復雜。補充的靈活性和很大程度上減少的“沒有執行”代碼是有力的好處。下面的代碼,在類型MockOrderDaoFactory.cs中找到的,展示了IOrderDao是怎樣和Rhino Mocks一起模擬的。本質上,它建立了一個“靜態”模擬,或者一個stub,事實上,在變元中經過了什么并不重要:它總是返回同一個例子命令(由TestOrdersFactory創建)。但是和Rhino Mocks模擬并沒有限制在dumb reflexes上,例如:這個可以像要求的一樣有響應。
public IOrderDao CreateMockOrderDao() { MockRepository mocks = new MockRepository(); IOrderDao mockedOrderDao = mocks.CreateMock<IOrderDao>(); Expect.Call(mockedOrderDao.GetByExample(null)).IgnoreArguments() .Return(new TestOrdersFactory().CreateOrders()); mocks.Replay(mockedOrderDao); return mockedOrderDao; }