可以看到BaseOrderDetail中的Count方法依赖于自身的属性PriceProvider,而该属性本应该是根据订单的类型指定对应的VanclProvder类的实例。如果此时,我们再运行单元测试,必然会引发空引用异常,原因是此时的PriceProvider属性并没有赋值。(在实际运行的代码中可以使用例如Autofac或者Unity之类的IOC容器实现初始化)
因此,我们需要在单元测试中添加Mock对象,来隔离对外部对象的依赖。修改后的单元测试代码如下:
/// <summary> ///A test for Count ///</summary> [TestMethod()] public void TestCount() { // Arrange BaseOrderDetail target = new ClothingOrderDetail(); decimal expected = 5555m; target.Amount = 100; Mock<IPriceProvide> mockPriceProvider = new Mock<IPriceProvide>(); mockPriceProvider.Setup(e => e.QueryPriceByProductID(It.IsAny<Guid>())).Returns(55.55m); target.PriceProvider = mockPriceProvider.Object; // Action decimal actual = target.Count(); // Assert Assert.AreEqual(expected, actual); } |
至此,两个单元测试都能顺利的通过验证。
三、当需求发生改变时,修改单元测试以及实现代码
我们假定我们的开发已经进入到一定阶段(已经有了两个类和方法的实现,并已经稳定),这时候需求发生了变更,我们来看下此时应该如何进行操作。
第一种情况,需求变更为,如果订单明细是ClothingOrderDetail类型时,Count方法需要在价格乘以数量之后增加额外的10元邮费。这种情况,其实只需要在ClothingOrderDetail类中重载BaseOrderDetail的Count方法即可,因此编写新的单元测试和方法实现即可。
从这里可以注意到一点单元测试的特性:虽然我们修改了BaseOrderDetail的需求和实现,但ProductOrder中Count方法的单元测试并无法检测到这种变化,甚至BaseOrderDetail的Count方法修改后会引发异常,也无法通过单元测试得知BaseOrderDetail中Count方法产生的异常会到只ProductOrder中的Count方法也会发生异常。这是单元测试的局限性。
第二种情况,需求变更为,如果订单的总额度超过5000,可以享受减免100的优惠。此时,可以先修改ProductOrder.Count方法的单元测试中的期望值为8648.55,再修改实际的代码实现如下:
/// <summary> /// Count the total of the all orders. /// </summary> /// <returns></returns> public decimal Count() { decimal result; result = this.OrderDetails.Sum(e => e.Count()); // Rreat Offer if (result >= 5000) { result = result - 100; } return result; } |