18. productView,
19. navigationService,
20. productService,
21. basketService);
22.
23. // Act
24. productPresenter.Initialize();
25.
26. // Assert
27. Assert.IsNotNull(productView.Product);
28. Assert.IsTrue(productView.IsInBasket);
29.}
Test #2
01.[TestMethod]
02.public void InitializeWithValidProductIDReturnsView()
03.{
04. // Arrange
05. var view = Mock.Create();
06. Mock.Arrange(() => view.ProductID).Returns("spr-product");
07.
08. var mock = new MockProductPresenter(view);
09.
10. // Act
11. mock.Presenter.Initialize();
12.
13. // Assert
14. Assert.IsNotNull(mock.Presenter.View.Product);
15. Assert.IsTrue(mock.Presenter.View.IsInBasket);
16.}
我相信Test #2是更容易理解的,不是嗎?而Test #1的可讀性不那么強的原因就是有太多的創建測試的代碼。在Test #2中,我把復雜的構建測試的邏輯提取到了ProductPresenter類里,從而使測試代碼可讀性更強。
為了把這個概念說的更清楚,讓我們來看看測試中引用的方法:
01.public void Initialize()
02.{
03. string productID = View.ProductID;
04. Product product = _productService.GetByID(productID);
05.
06. if (product != null)
07. {
08. View.Product = product;
09. View.IsInBasket = _basketService.ProductExists(productID);
10. }
11. else
12. {
13. NavigationService.GoTo("/not-found");
14. }
15.}
這個方法依賴于View, ProductService, BasketService and NavigationService等類,這些類都要模擬或臨時構造出來。當遇到這樣有太多的依賴關系時,這種需要寫出準備代碼的副作用就會顯現出來,正如上面的例子。
請注意,這還只是個很保守的例子。更多的我看到的是一個類里有模擬一、二十個依賴的情況。
下面就是我在測試中提取出來的模擬ProductPresenter的MockProductPresenter類:
01.public class MockProductPresenter
02.{
03. public IBasketService BasketService { get; set; }
04. public IProductService ProductService { get; set; }
05. public ProductPresenter Presenter { get; private set; }
06.
07. public MockProductPresenter(IProductView view)
08. {
09. var productService = Mock.Create();
10. var navigationService = Mock.Create();
11. var basketService = Mock.Create();
12.
13. // Setup for private methods
14. Mock.Arrange(() => productService.GetByID("spr-product")).Returns(new Product());
15. Mock.Arrange(() => basketService.ProductExists("spr-product")).Returns(true);
16. Mock.Arrange(() => navigationService.GoTo("/not-found")).OccursOnce();
17.
18. Presenter = new ProductPresenter(
19. view,
20. navigationService,
21. productService,
22. basketService);
23. }
24.}
因為View.ProductID的屬性值決定著這個方法的邏輯走向,我們向MockProductPresenter類的構造器里傳入了一個模擬的View實例。這種做法保證了當產品ID改變時自動判斷需要模擬的依賴。
我們也可以用這種方法處理測試過程中的細節動作,就像我們在第二個單元測試里的Initialize方法里處理product==null的情況:
01.[TestMethod]
02.public void InitializeWithInvalidProductIDRedirectsToNotFound()
03.{
04. // Arrange
05. var view = Mock.Create();
06. Mock.Arrange(() => view.ProductID).Returns("invalid-product");
07.
08. var mock = new MockProductPresenter(view);
09.
10. // Act
11. mock.Presenter.Initialize();
12.
13. // Assert
14. Mock.Assert(mock.Presenter.NavigationService);
15.}
這隱藏了一些ProductPresenter實現上的細節處理,測試方法的可讀性是第一重要的。
原文轉自:http://www.vaikan.com/top-5-tdd-mistakes/