测试驱动开发上的五大错误
作者:网络转载 发布时间:[ 2013/3/15 10:56:41 ] 推荐标签:
我相信Test #2是更容易理解的,不是吗?而Test #1的可读性不那么强的原因是有太多的创建测试的代码。在Test #2中,我把复杂的构建测试的逻辑提取到了ProductPresenter类里,从而使测试代码可读性更强。
为了把这个概念说的更清楚,让我们来看看测试中引用的方法:
public void Initialize()
{
string productID = View.ProductID;
Product product = _productService.GetByID(productID);
if (product != null)
{
View.Product = product;
View.IsInBasket = _basketService.ProductExists(productID);
}
else
{
NavigationService.GoTo("/not-found");
}
}
这个方法依赖于View, ProductService, BasketService and NavigationService等类,这些类都要模拟或临时构造出来。当遇到这样有太多的依赖关系时,这种需要写出准备代码的副作用会显现出来,正如上面的例子。
请注意,这还只是个很保守的例子。更多的我看到的是一个类里有模拟一、二十个依赖的情况。
下面是我在测试中提取出来的模拟ProductPresenter的MockProductPresenter类:
public class MockProductPresenter
{
public IBasketService BasketService { get; set; }
public IProductService ProductService { get; set; }
public ProductPresenter Presenter { get; private set; }
public MockProductPresenter(IProductView view)
{
var productService = Mock.Create<IProductService>();
var navigationService = Mock.Create<INavigationService>();
var basketService = Mock.Create<IBasketService>();
// Setup for private methods
Mock.Arrange(() => productService.GetByID("spr-product")).Returns(new Product());
Mock.Arrange(() => basketService.ProductExists("spr-product")).Returns(true);
Mock.Arrange(() => navigationService.GoTo("/not-found")).OccursOnce();
Presenter = new ProductPresenter(
view,
navigationService,
productService,
basketService);
}
}
因为View.ProductID的属性值决定着这个方法的逻辑走向,我们向MockProductPresenter类的构造器里传入了一个模拟的View实例。这种做法保证了当产品ID改变时自动判断需要模拟的依赖。
我们也可以用这种方法处理测试过程中的细节动作,像我们在第二个单元测试里的Initialize方法里处理product==null的情况:
[TestMethod]
public void InitializeWithInvalidProductIDRedirectsToNotFound()
{
// Arrange
var view = Mock.Create<IProductView>();
Mock.Arrange(() => view.ProductID).Returns("invalid-product");
var mock = new MockProductPresenter(view);
// Act
mock.Presenter.Initialize();
// Assert
Mock.Assert(mock.Presenter.NavigationService);
}
这隐藏了一些ProductPresenter实现上的细节处理,测试方法的可读性是第一重要的。

sales@spasvo.com