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实现上的细节处理,测试方法的可读性是第一重要的。
3、一次测试太多的项目
看看下面的单元测试,请在不使用“和”这个词的情况下描述它:
01.[TestMethod]
02.public void ProductPriceTests()
03.{
04. // Arrange
05. var product = new Product()
06. {
07. BasePrice = 10m
08. };
09.
10. // Act
11. decimal basePrice = product.CalculatePrice(CalculationRules.None);
12. decimal discountPrice = product.CalculatePrice(CalculationRules.Discounted);
13. decimal standardPrice = product.CalculatePrice(CalculationRules.Standard);
14.
15. // Assert
16. Assert.AreEqual(10m, basePrice);
17. Assert.AreEqual(11m, discountPrice);
18. Assert.AreEqual(12m, standardPrice);
19.}
我只能这样描述这个方法:
“测试中计算基价,打折价和标准价是都能否返回正确的值。”
这是一个简单的方法来判断你是否一次测试了过多的内容。上面这个测试会有三种情况导致它失败。如果测试失败,我们需要去找到那个/哪些出了错。
理想情况下,每一个方法都应该有它自己的测试,例如:
01.[TestMethod]
02.public void CalculateDiscountedPriceReturnsAmountOf11()
03.{
04. // Arrange
05. var product = new Product()
06. {
07. BasePrice = 10m
08. };
09.
10. // Act
11. decimal discountPrice = product.CalculatePrice(CalculationRules.Discounted);
12.
13. // Assert
14. Assert.AreEqual(11m, discountPrice);
15.}
16.
17.[TestMethod]
18.public void CalculateStandardPriceReturnsAmountOf12()
19.{
20. // Arrange
21. var product = new Product()
22. {
23. BasePrice = 10m
24. };
25.
原文转自:http://www.aqee.net/top-5-tdd-mistakes/