单元测试之Mock(Moq)
Mock翻译为“嘲弄”,其实就是伪造一个对象用于测试。在单元测试中,被测试方法依赖于其他对象时,为了测试简单一般“伪造”一个这个对象;这样做的目的:
- 不用考虑依赖对象的复杂性(方便准备测试数据)
- 只专注测试被测试方法,不将单元测试扩充到测试依赖对象
打折算法测试
商场中的商品类:
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 { set; get; }
}
打折接口:
public interface IDiscountHelper {
decimal ApplyDiscount(decimal totalParam);
}
被测试的打折算法
public class LinqValueCalculator : IValueCalculator {
private IDiscountHelper discounter;
public LinqValueCalculator(IDiscountHelper discountParam) {
discounter = discountParam;
}
public decimal ValueProducts(IEnumerable<Product> products) {
return discounter.ApplyDiscount(products.Sum(p => p.Price));
}
显然,被测试方法ValueProducts
依赖打折接口IDiscountHelper
,这里通过伪造一个实现IDiscountHelper
接口的对象。
Moq功能介绍
-
创建伪造对象
Mock<IDiscountHelper> mock = new Mock<IDiscountHelper>();
-
设置伪造对象的方法
mock.Setup(m => m.ApplyDiscount(It.IsAny<decimal>()))
-
参数限制
t.IsAny<decimal>()
通过It
限制这里列出几个限制表达式 解释 例子 It.Is (predicate) 通过predicate返回bool值判断限制测试 It.Is<decimal>(v => v == 0)
It.IsAny () T类型的任意值 It.IsAny()
It.IsInRange (TValue from, TValue to, Range rangeKind) T类型参数的值在范围内或外(rangeKind参数决定) It.IsInRange<int>(1, 100, Range.Inclusive)
-
设置返回值
.Returns<decimal>(total => total);
构造单元测试
[TestClass]
public class UnitTest2 {
private Product[] createProduct(decimal value) {
return new[] { new Product { Price = value } };
}
[TestMethod]
[ExpectedException(typeof(System.ArgumentOutOfRangeException))]
public void Pass_Through_Variable_Discounts() {
// arrange
Mock<IDiscountHelper> mock = new Mock<IDiscountHelper>();
mock.Setup(m => m.ApplyDiscount(It.IsAny<decimal>()))
.Returns<decimal>(total => total);
mock.Setup(m => m.ApplyDiscount(It.Is<decimal>(v => v == 0)))
.Throws<System.ArgumentOutOfRangeException>();
mock.Setup(m => m.ApplyDiscount(It.Is<decimal>(v => v > 100)))
.Returns<decimal>(total => (total * 0.9M));
mock.Setup(m => m.ApplyDiscount(It.IsInRange<decimal>(10, 100,
Range.Inclusive))).Returns<decimal>(total => total - 5);
var target = new LinqValueCalculator(mock.Object);
// act
decimal FiveDollarDiscount = target.ValueProducts(createProduct(5));
decimal TenDollarDiscount = target.ValueProducts(createProduct(10));
decimal FiftyDollarDiscount = target.ValueProducts(createProduct(50));
decimal HundredDollarDiscount = target.ValueProducts(createProduct(100));
decimal FiveHundredDollarDiscount = target.ValueProducts(createProduct(500));
// assert
Assert.AreEqual(5, FiveDollarDiscount, "$5 Fail");
Assert.AreEqual(5, TenDollarDiscount, "$10 Fail");
Assert.AreEqual(45, FiftyDollarDiscount, "$50 Fail");
Assert.AreEqual(95, HundredDollarDiscount, "$100 Fail");
Assert.AreEqual(450, FiveHundredDollarDiscount, "$500 Fail");
target.ValueProducts(createProduct(0));
}
}