测试驱动开发上的五大错误(3)

发表于:2013-06-18来源:外刊IT评论作者:不详点击数: 标签:tdd
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.} 这个

  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/