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

发表于:2013-06-18来源:外刊IT评论作者:不详点击数: 标签:tdd
14. return null; 15. } 16. 17. public IEnumerable GetProducts() 18. { 19. throw new NotImplementedException(); 20. } 21.} 在第一种方法里,我们写了两个不同的IProductRepository模拟方法

  14. return null;

  15. }

  16.

  17. public IEnumerable GetProducts()

  18. {

  19. throw new NotImplementedException();

  20. }

  21.}

  在第一种方法里,我们写了两个不同的IProductRepository模拟方法,而在第二种方法里,我们的逻辑变得有些复杂。如果我们在这些逻辑中犯了错,那我们的测试就没法得到正确的结果,这又为我们的调试增加了额外的负担,我们需要找到是业务代码出来错还是测试代码不正确。

  你也许还会质疑这些模拟代码中的这个没有任何用处的 GetProducts()方法,它是干什么的?因为IProductRepository接口里有这个方法,我们不得不加入这个方法以让程序能编译通过——尽管在我们的测试中这个方法根本不是我们考虑到对象。

  使用这样的测试方法,我们不得不写出大量的临时模拟类,这无疑会让我们在维护时愈加头痛。这种时候,使用一个模拟框架,比如JustMock,将会节省我们大量的工作。

  让我们重新看一下之前的这个测试例子,这次我们将使用一个模拟框架:

  01.[TestMethod]

  02.public void GetProductWithValidIDReturnsProduct()

  03.{

  04. // Arrange

  05. IProductRepository productRepository = Mock.Create();

  06. Mock.Arrange(() => productRepository.GetByID("spr-product")).Returns(new Product());

  07. ProductService productService = new ProductService(productRepository);

  08.

  09. // Act

  10. Product product = productService.GetByID("spr-product");

  11.

  12. // Assert

  13. Assert.IsNotNull(product);

  14.}

  15.

  16.[TestMethod]

  17.public void GetProductWithInValidIDThrowsException()

  18.{

  19. // Arrange

  20. IProductRepository productRepository = Mock.Create();

  21. ProductService productService = new ProductService(productRepository);

  22.

  23. // Act & Assert

  24. Assert.Throws(() => productService.GetByID("invalid-id"));

  25.}

  有没有注意到我们写的代码的减少量?在这个例子中代码量减少49%,更准确的说,使用模拟框架测试时代码是28行,而没有使用时是57行。我们还看到了整个测试方法变得可读性更强了!

  2、测试代码组织的太松散

  模拟框架让我们在模拟测试中的生成某个依赖类的工作变得非常简单,但有时候太轻易实现也容易产生坏处。为了说明这个观点,请观察下面两个单元测试,看看那一个容易理解。这两个测试程序是测试一个相同的功能:

  Test #1

  01.TestMethod]

  02.public void InitializeWithValidProductIDReturnsView()

  03.{

  04. // Arrange

  05. IProductView productView = Mock.Create();

  06. Mock.Arrange(() => productView.ProductID).Returns("spr-product");

  07.

  08. IProductService productService = Mock.Create();

  09. Mock.Arrange(() => productService.GetByID("spr-product")).Returns(new Product()).OccursOnce();

  10.

  11. INavigationService navigationService = Mock.Create();

  12. Mock.Arrange(() => navigationService.GoTo("/not-found"));

  13.

  14. IBasketService basketService = Mock.Create();

  15. Mock.Arrange(() => basketService.ProductExists("spr-product")).Returns(true);

  16.

  17. var productPresenter = new ProductPresenter(

  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);

原文转自:http://www.aqee.net/top-5-tdd-mistakes/