8.MVC工具之Moq

  • 时间:2019-06-02
  • 作者:Charles
  • 热度:3081

前文的单元测试示例很简单,原因之一是因为测试的是一个不依赖于其他类而起作用的单一的类。在实际项目中,往往还需要测试一些不能孤立运行的对象。在这些情况下,需要将注意力集中与感兴趣的类或方法上,才能不必对依赖类业进行隐式测试。

一个有用的方法是使用模仿对象,它能够以一种特殊而受控的方式,来模拟项目中实际对象的功能。模仿对象能够缩小测试的侧重点,以使用户只检查感兴趣的功能。

一:理解问题

本小节将对LinqValueCaculator类进行单元测试,添加一个新的单元测试类。在解决方案资源管理器窗口中右击测试项目,从弹出菜单中选择“添加—单元测试”,编辑测试方法,如下图所示。

                                             

现在面临的问题是,LinqValuecaculator类依赖于IDiscountHelper接口的实现才能进行操作。此例使用了MinimumDiscountHelper类,它表现了两个不同的问题。

第一个问题是单元测试变得复杂和脆弱。为了创建一个能够进行工作的单元测试,需要考虑IDiscountHelper实现中的折扣逻辑,以便判断出ValueProducts方法的预期值。脆弱来自这样一个事实:一旦该现实中的折扣逻辑发生变化,测试便会失败。

第二个也是最令人担忧的问题是已经延展了这一单元测试的范围,使它隐式地包含了MinimumDiscountHelper类。当单元测试失败时,用户不知道问题时出在LinqValuecaculator类中还是MinimumDiscountHelper类中。

二:对单元测试添加模仿对象

使用NuGet包管理器搜索添加Moq包到单元测试项目下。对单元测试添加模仿对象,其目的是告诉Moq,用户是想使用哪一种对象。对它的行为进行配置,然后将该对象运用于测试目的。修改UnitTest2如下图所示。

创建模仿对象

第一步是要告诉Moq,用户想使用的是哪种模仿对象。Moq十分依赖于泛型的类型参数,从以下语句可以看出这种参数的使用方式,这是告诉Moq,要模仿的是IDiscountHelper实现。

Mock<IDiscountHelper> mock = new Mock<IDiscountHelper>();

创建以恶强类型的Mock< IDiscountHelper >对象,目的是告诉Moq库,它处理的是哪种类型。

选择方法

除了创建强类型的Mock对象外,还需要指定它的行为方式,这是模仿过程的核心,它可以建立模仿所需的基准行为,用户可以将这种行为用于对单元测试中目标对象的功能进行测试。以下是单元测试中的语句,它为模仿对象建立了用户所希望的行为。

mock.Setup(m => m.ApplyDiscount(It.IsAny<decimal>())).Returns<decimal>(total=>total);

用Setup方法给模仿对象添加一个方法,Moq使用LINQ和lambda表达式进行工作,在调用setup方法时Moq会传递要求它实现的接口。对于该单元测试,希望定义ApplyDiscount方法的行为,它是IDiscountHelper接口的唯一方法,也是对LinqValuecaculator类进行单元测试的方法。

必须告诉Moq感兴趣的参数值是什么,这是要用It类来做的事情,如下所示。

m.ApplyDiscount(It.IsAny<decimal>())

这个It类定义了许多以泛型类型参数进行使用的方法。此例用decimal作为泛型类型调用了IsAny方法。这是告诉Moq,当以任何十进制值为参数来调用ApplyDiscount方法时,它应该运用我们定义的这一行为。

定义结果

Returns方法让用户指定在调用模仿方法时要返回的结果,其类型参数用以指定结果的类型,而用lambda表达式来指定结果。如下所示。

Returns<decimal>(total=>total);

通过调用带有decimal类型参数的Returns方法,这是告诉Moq要返回一个十进制的值。对于lambda表达式,Moq传递了一个在ApplyDiscount方法中接收类型值,此例创建了一个穿透方法,该方法返回了传递给模仿的ApplyDiscount方法的值,并未对这个值执行任何操作。

使用模仿对象

最后一步是在单元测试中使用这个模仿对象,通过读取Mock<IDiscountHelper>对象的Object属性值来实现。

var target = new LinqValueCalculator(mock.Object);

总结一下,在上述示例中,Object属性返回IDiscountHelper接口的实现,该实现中的ApplyDiscount方法返回它传递的十进制参数的值。

这种方式使用Moq的好处是,单元测试只检查LinqValueCalculator对象的行为,并不依赖任何Models文件夹中IdiscountHelper接口的真实实现。这意味着测试失败时,用户便知道问题出现在LinqValueCalculator实现中,或是建立模仿对象的方式中。而解决源自这些方面的问题,比处理实际对象链及其相互交互更加简单而容易,

三:创建更复杂的模仿对象

上例展示的是一个十分简单的模仿对象,但Moq最漂亮的部分是快速建立复杂行为以便对不同情况进行测试的能力。给UnitTest2添加新的单元测试,让它模仿更为复杂的IDiscountHelper接口实现,如下图所示。

在单元测试期间,复制一个模型类型期望的行为似乎是在做一件奇怪的事情,但这能够完美演示Moq的一些不同用法。

可以看出,根据所收到的参数,定义了ApplyDiscount的四种行为,最简单的行为是全匹配,它直接返回任意的decimal值,如下所示。

mock.Setup(m => m.ApplyDiscount(It.IsAny<decimal>())).Returns<decimal>(total => total);

这是上一示例的同一行为,把它放在这儿是因为调用Setup方法的顺序会影响模仿对象的行为。Moq会以相反的顺序评估所给定的行为,因此会考虑调用最后一个Setup方法,这意味着,用户必须按从最一般到最特殊的顺序,小心地创建模仿行为,It.IsAny<decimal>()是此例定义的最一般的条件,因而首先运用它。如果颠倒调用Setup的顺序,该行为将能匹配对ApplyDiscount方法的所有调用,并生成错误的模仿结果。



博主声明

1、本博客主要为原创文章,转载请注明出处。

2、部分文章来自网络,已注明出处,如有侵权请与本人联系。

3、如果文章内容有误,或者您有其他更好的意见、建议请给我留言,我会及时处理!