在领域驱动模型中,往往使用依赖注入,大多借助于接口实现。在ASP.NET MVC3中一个典型的应用就是在领取驱动模型中通过IRepository接口,调用实际Repository代码。那么在TDD开发过程中,我们往往还没有实现Repository代码,为了测试领域驱动模型规则(rule)的正确性,比较笨拙的方法是弄一个假设的Repository实现,现在可以借助Moq插件去快速实现Repository。
具体做法如下:
1、假设要操作产品(Product),那么定义模型:
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
2、然后定义存储库(Repository),引入接口解耦
public interface IProductRepository
{
IEnumerable<Product> GetProducts();
void UpdateProduct(Product p);
}
注意:虚拟的实现(笨拙)
public class FakeRepository : IProductRepository
{
Product[] prods ={
new Product {Name="A",Price =50m},
new Product {Name="B",Price=60m},
new Product {Name ="C",Price =70m}
};
public IEnumerable<Product> GetProducts()
{
//mock.Setup(e => e.GetProducts()).Returns(prods);
return prods;
}
public int UpdatedProductCount { get; set; }
public void UpdateProduct(Product p)
{
foreach (Product pro in prods.Where(e => e.Name == p.Name).Select(e => e))
{
pro.Price = p.Price;
}
UpdatedProductCount++;
}
public decimal GetTotalValue()
{
return prods.Sum(e => e.Price);
}
}
3、定义Domain模型的规则和操作
public interface IPriceReducer
{
void priceReduce(decimal price);
}
public class MyPriceReducer : IPriceReducer
{
private IProductRepository ipr;
public MyPriceReducer(IProductRepository ipr)
{
this.ipr = ipr;
}
public void priceReduce(decimal price)
{
foreach (Product p in ipr.GetProducts())
{
p.Price = Math.Max(p.Price-price, 1);
ipr.UpdateProduct(p);
}
}
}
测试代码:
[TestMethod]
public void AllPricesAreChanged) {
// Arrange
FakeRepository repo = new FakeRepository();
decimal reductionAmount = 10;
IEnumerable<decimal> prices = repo.GetProducts().Select(e => e.Price);
decimal[] initialPrices = prices.ToArray();
MyPriceReducer target = new MyPriceReducer(repo);
// Act
target.ReducePrices(reductionAmount);
prices.Zip(initialPrices, (p1, p2) => {
if (p1 == p2) {
Assert.Fail();
}
return p1;
});
}
使用Moq进行打桩测试:
首先,添加Moq.dll(在测试项目中),
然后,由于没有FakeRepository实现,需要准备数据:
[TestInitialize()]
public void MyTestInitialize()
{
prods = new[] {
new Product {Name="A",Price =50m},
new Product {Name="B",Price=60m},
new Product {Name ="C",Price =70m}
};
}
最好在测试方法中这样写:
[TestMethod()]
public void AllPriceAreChanged()
{
Mock<IProductRepository> mipr = new Mock<IProductRepository>();
mipr.Setup(e => e.GetProducts()).Returns(prods);
MyPriceReducer target = new MyPriceReducer(mipr.Object); // TODO: 初始化为适当的值
Decimal price = 10m; // TODO: 初始化为适当的值
//decimal initialTotal = prods.Sum(e => e.Price);
target.priceReduce(price);
foreach (Product p in prods)
{
mipr.Verify(m => m.UpdateProduct(p), Times.Once());
}
}
注意:
1、要加using Moq;
2、Mock<>模拟
3、setup(没有实现的接口方法).returns(假设方法返回的数据),实现了不用实现方法体直接指定返回数据的核心思想
4、在domain模型中引用mock.object,达到调用具体实现的目的。