准备项目 打开VS,创建ASP.NET Web应用程序 ,项目名称为EssentialTools ,选择MVC空模板。
创建模型类 在Models文件夹添加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 ; } }
在Models文件夹添加LinqValueCalculator 类,它将计算Product 对象集合的总价。
public class LinqValueCalculator { public decimal ValueProducts (IEnumerable<Product> products ) { return products.Sum(p => p.Price); } }
在Models文件夹添加ShoppingCart 类,它表示了Product 对象的集合 ,并且用LinqValueCalculator 来确定总价。
public class ShoppingCart { private LinqValueCalculator calc; public ShoppingCart (LinqValueCalculator calcParam ) { calc = calcParam; } public IEnumerable<Product> products { get ; set ; } public decimal CalculateProductTotal ( ) { return calc.ValueProducts(products); } }
添加控制器 添加HomeController 。在Index 动作方法创建Product对象数组 ,并用LinqValueCalculator 对象产生总价的值,将其传递给View 。
public class HomeController : Controller { private Product[] products = { new Product { Name = "Kayak" , Category = "Watersports" , Price = 275 M }, new Product { Name = "Lifejacket" , Category = "Watersports" , Price = 48.95 M }, new Product { Name = "Soccer ball" , Category = "Soccer" , Price = 19.50 M }, new Product { Name = "Corner flag" , Category = "Soccer" , Price = 34.95 M } }; public ActionResult Index ( ) { LinqValueCalculator calc = new LinqValueCalculator(); ShoppingCart cart = new ShoppingCart(calc) { products = products }; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); } }
添加视图 添加Index 视图,接收的模型类为decimal 。
@model decimal @{ Layout = null; } <!DOCTYPE html> <html > <head > <meta name ="viewport" content ="width=device-width" /> <title > Index</title > </head > <body > <div > Total value is $@Model </div > </body > </html >
启动项目,显示结果为$378.40 。
使用Ninject 依赖注入(DI) 的思想是,对MVC应用程序中的组件进行解耦 ,这是通过接口 与DI容器 相结合来实现的。DI容器创建了对象实例,这是通过创建对象所依赖的接口并将其注入构造器而实现的。
理解问题
ShoppingCart类 与LinqValueCalculator类 是紧耦合的
HomeController类 与ShoppingCart类 和LinqValueCalculator类 都是紧耦合的。
这意味着,如果想替换LinqValueCalculator类 ,就必须在与它有紧耦合关系 的类中找出对它的引用,并进行修改 。
运用接口 在Models文件夹添加IValueCalculator接口 。
public interface IValueCalculator { decimal ValueProducts (IEnumerable<Product> products ) ; }
然后在LinqValueCalculator类 中实现这一接口。
public class LinqValueCalculator : IValueCalculator { public decimal ValueProducts (IEnumerable<Product> products ) { return products.Sum(p => p.Price); } }
在ShoppingCart类 中运用接口。于是便解除了 ShoppingCart类与LinqValueCalculator类之间的紧耦合 (两个类不再具有直接联系)。
public class ShoppingCart { private IValueCalculator calc; public ShoppingCart (IValueCalculator calcParam ) { calc = calcParam; } public IEnumerable<Product> products { get ; set ; } public decimal CalculateProductTotal ( ) { return calc.ValueProducts(products); } }
但是,在HomeController中,与LinqValueCalculator类还是紧耦合 。(仍然需要用new关键字 创建LinqValueCalculator对象)
public ActionResult Index ( ) { IValueCalculator calc = new LinqValueCalculator(); ShoppingCart cart = new ShoppingCart(calc) { products = products }; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); }
因此,通过Ninject 可以指定LinqValueCalculator作为IValueCalculator的实现 ,这样,就不需要在HomeController使用new关键字 创建LinqValueCalculator 对象(解除HomeController与LinqValueCalculator的紧耦合)。
将Ninject添加到VS项目 输入以下Nuget命令:
Install-Package Ninject -version 3.0.1.10
Install-Package Ninject.Web.Common -version 3.0.0.7
Install-Package Ninject.MVC3 -version 3.0.0.6
第一行命令用于安装Ninject内核包,其他命令用于安装内核的扩展包。
Ninject初步 在HomeController中,给Index 动作方法添加基本的Ninject 功能。
public ActionResult Index ( ) { IKernel ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); IValueCalculator calc = ninjectKernel.Get<IValueCalculator>(); ShoppingCart cart = new ShoppingCart(calc) { products = products }; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); }
第一步,创建一个Ninject的内核实例 。它负责解析依赖项 并创建新的对象 (当需要一个对象时,将使用这个内核而不是使用new关键字)。可以通过创建一个StandardKernel (标准内核)类的实例来完成。
第二步,配置Ninject内核 。指定每个接口 所希望使用的实现对象 ,Bind方法 指定要使用的接口 ,To方法 指定希望实例化的实现类 。
第三步,使用Ninject创建对象 。调用内核的Get方法 ,返回指定的实现类型的一个实例。
建立MVC的依赖项注入 创建依赖项解析器 首先要创建一个自定义的依赖项解析器 。MVC框架需要使用依赖项解析器来创建类的实例,包括控制器实例。
项目右键,新建Infrastructure文件夹 ,用于放置MVC应用程序中不适合放在其他文件夹的类。新建NinjectDependencyResolver类 。
public class NinjectDependencyResolver : IDependencyResolver { private IKernel kernel; public NinjectDependencyResolver (IKernel kernelParam ) { kernel = kernelParam; AddBindings(); } public object GetService (Type serviceType ) { return kernel.TryGet(serviceType); } public IEnumerable<object > GetServices (Type serviceType ) { return kernel.GetAll(serviceType); } private void AddBindings ( ) { kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); } }
注意 :
NinjectDependencyResolver类实现了IDependencyResolver接口 ,它属于System.Mvc命名空间 ,用于MVC框架获取需要的对象 。
MVC框架在需要类实例以便对一个传入的请求进行服务时,会调用GetService 或GetServices 方法(通过调用Ninject的TryGet和GetAll方法)。
AddBindings方法 配置了IValueCalculator接口 和LinqValueCalculator类 之间的关系(建立Ninject绑定)。
注册依赖项解析器 使用Nuget 添加Ninject包 的时候,会在App_Start文件夹 创建一个NinjectWebCommon.cs 文件。它定义了应用程序启动时会自动调用的一些方法 ,目的是将它们集成到ASP.NET的请求生命周期之中。
在NinjectWebCommon 类的RegisterServices方法 中,用SetResolver静态方法 将NinjectDependencyResolver的实例 注册为MVC框架的解析器。
private static void RegisterServices (IKernel kernel ) { System.Web.Mvc.DependencyResolver.SetResolver(new EssentialTools.Infrastructure.NinjectDependencyResolver(kernel)); }
重构Home控制器 修改HomeController,对IValueCalculator接口 声明一个依赖项 。
public class HomeController : Controller { private IValueCalculator calc; public HomeController (IValueCalculator calcParam ) { calc = calcParam; } private Product[] products = { new Product { Name = "Kayak" , Category = "Watersports" , Price = 275 M }, new Product { Name = "Lifejacket" , Category = "Watersports" , Price = 48.95 M }, new Product { Name = "Soccer ball" , Category = "Soccer" , Price = 19.50 M }, new Product { Name = "Corner flag" , Category = "Soccer" , Price = 34.95 M } }; public ActionResult Index ( ) { ShoppingCart cart = new ShoppingCart(calc) { products = products }; decimal totalValue = cart.CalculateProductTotal(); return View(totalValue); } }
这称为构造器注入 ,是依赖项注入的一种形式。这样就能打破HomeController 与LinqValueCalculator 之间的紧耦合关系。
创建依赖项链 当要求Ninject创建一个类型时,它会检查 是否还声明了自己的依赖项 。如果有额外的依赖项,Ninject会自动地解析 这些依赖项,并创建所需要的所有类的实例 ,以这种方式处理依赖项链 。
在Models文件夹中,添加Discount.cs 文件,并定义一个新的接口和其实现类。DefaultDiscountHelper类 实现了IDiscountHelper接口 ,并运用固定的10%折扣。
public interface IDiscountHelper { decimal ApplyDiscount (decimal totalParam ) ; } public class DefaultDiscountHelper : IDiscountHelper { public decimal ApplyDiscount (decimal totalParam ) { return (totalParam - (10 m / 100 m * totalParam)); } }
修改LinqValueCalculator类 ,以使它执行计算时,使用IDiscountHelper接口 。
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)); } }
在NinjectDependencyResolver类的AddBindings方法 中,将IDiscountHelper接口绑定到它的实现 。
private void AddBindings ( ) { kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); }
注意 :上面代码已经创建了一个依赖项链 。HomeController依赖于IValueCalculator接口,Ninject用LinqValueCalculator类对该接口进行解析。LinqValueCalculator又依赖于IDiscountHelper接口,Ninject又用DefaultDiscountHelper类对其进行解析。
指定属性和构造器参数值 在将接口与其实现进行绑定时,可以为属性提供一些值方面的细节 ,以便对Ninject创建的对象进行配置 。
修改DefaultDiscountHelper 类,定义一个DiscountSize属性 ,用于计算折扣量。
public class DefaultDiscountHelper : IDiscountHelper { public decimal DiscountSize { get ; set ; } public decimal ApplyDiscount (decimal totalParam ) { return (totalParam - (DiscountSize / 100 m * totalParam)); } }
在NinjectDependencyResolver类中,用WithPropertyValue方法 为DiscountSize属性设置一个值。
private void AddBindings ( ) { kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize" , 50 M); }
这样,不需要修改其他绑定,该属性值会按照DefaultDiscountHelper的结构进行设置,起到了半价的效果。
如果需要设置多个属性值 ,可以链式调用WithPropertyValue 方法,也可以用类构造器参数 去实现。
修改DefaultDiscountHelper类,以使折扣大小 作为构造器参数 进行传递。
public class DefaultDiscountHelper : IDiscountHelper { public decimal discountSize; public DefaultDiscountHelper (decimal discountParam ) { discountSize = discountParam; } public decimal ApplyDiscount (decimal totalParam ) { return (totalParam - (discountSize / 100 m * totalParam)); } }
在Addbindings方法中用WithConstructorArgument方法 来指定构造器参数的值 。
private void AddBindings ( ) { kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam" , 50 M); }
使用条件绑定 在Models文件夹添加FlexibleDiscountHelper类 ,根据总额大小运用不同的折扣。
public class FlexibleDiscountHelper : IDiscountHelper { public decimal ApplyDiscount (decimal totalParam ) { decimal discount = totalParam > 100 ? 70 : 25 ; return (totalParam - (discount / 100 m * totalParam)); } }
在AddBindings方法中,添加绑定 。当Ninject内核要创建一个LinqValueCalculator对象 时,应该使用FlexibleDiscountHelper类作为IDiscountHelper的实现。
private void AddBindings ( ) { kernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam" , 50 M); kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedInto<LinqValueCalculator>(); }
Ninject条件绑定方法如下表 :
方法
效果
When(谓词)
当“谓词”(一个lambda表达式)的结果为true时,实施绑定
WhenClassHas()
当被注入的类以注解属性进行注释,而其类型为T时,实施绑定
WhenInjectedInto()
当要被注入的类是类型T时,实施绑定
设置对象作用域 该Ninject特性有助于调整Ninject所建对象的生命周期 ,以满足应用程序的需求。默认情况下,Ninject会在每次请求一个对象时,为每个依赖项所需的各个对象创建一个新实例。
修改LinqValueCalculator 类的构造器 ,在每次创建一个新实例时,都向VS的输出窗口写一条消息 。
public class LinqValueCalculator : IValueCalculator { private IDiscountHelper discounter; private static int counter = 0 ; public LinqValueCalculator (IDiscountHelper discountParam ) { discounter = discountParam; System.Diagnostics.Debug.WriteLine(string .Format("Instance {0} created" , ++counter)); } public decimal ValueProducts (IEnumerable<Product> products ) { return discounter.ApplyDiscount(products.Sum(p => p.Price)); } }
修改HomeController,在构造器参数中,增加一个IValueCalculator接口的实现。
public HomeController (IValueCalculator calcParam, IValueCalculator calc2 ) { calc = calcParam; }
运行项目,观察VS的输出窗口,发现Ninject创建了LinqValueCalculator类的两个实例 。
对于某些类,如果希望在整个应用程序中共享一个单一的实例 。而对于另一些类,希望为接收到的每个HTTP请求,都创建一个新的实例 。可以在建立绑定时,通过作用域特性 来控制所创建对象的生命周期 。
在AddBindings方法中,将请求作用域运用于LinqValueCalculator类。
private void AddBindings ( ) { kernel.Bind<IValueCalculator>().To<LinqValueCalculator>().InRequestScope(); kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>().WithConstructorArgument("discountParam" , 50 M); kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>().WhenInjectedInto<LinqValueCalculator>(); }
Ninject的作用域方法如下表 :
名称
效果
InTransientScope()
与未指定作用域效果相同,为每一个被解析的依赖项创建一个新的对象(每依赖项一实例 )
InSingletonScope() ToConstant(object)
创建一个单一实例,使其共享于整个应用程序。如果使用InSingletonScope,或者为Ninject 提供ToConstant方法,Ninject便会创建这种实例(每应用一实例 )
InThreadScope()
创建一个单一实例,将其用于解析一个线程中各个对象的依赖项(每线程一实例 )
InRequestScope()
创建一个单一实例,用于解析一个HTTP请求中各个对象的依赖项(每请求一实例 )
VS的单元测试 可以使用VS附带的内置单元测试 ,也可以使用.NET单元测试包,如NUnit 。
在Models文件夹创建MinimumDiscountHelper.cs 文件,实现IDiscountHelper接口 。
public class MinimumDiscountHelper : IDiscountHelper { public decimal ApplyDiscount (decimal totalParam ) { throw new NotImplementedException(); } }
创建单元测试项目 右键解决方案名称,添加新项目 ,选择单元测试项目 ,项目名称为EssentialTools.Tests 。
右键EssentialTools.Tests 项目,添加EssentialTools项目的引用 。
添加单元测试 修改UnitTest1.cs 文件,添加测试方法。
[TestClass ] public class UnitTest1 { private IDiscountHelper getTestObject ( ) { return new MinimumDiscountHelper(); } [TestMethod ] public void Discount_Above_100 ( ) { IDiscountHelper target = getTestObject(); decimal total = 200 ; var discountedTotal = target.ApplyDiscount(total); Assert.AreEqual(total * 0.9 M, discountedTotal); } }
含有测试的类 用TestClass注解属性 注释,测试方法 用TestMethod注解属性 注释。
Assert类 有一些列可以在测试中使用的静态方法 ,该类位于Microsoft.VisualStudio.TestTools.UnitTesting 命名空间,包含了一些对建立和执行测试有用的其他类。(表格略)
注意 :Microsoft.VisualStudio.TestTools.UnitTesting 命名空间中有一个成员是ExpectedException属性 。这是一个断言,只当单元测试抛出ExceptionType参数指定类型的异常时,该断言才是成功的。
接下来对测试项目增加一些测试,以验证前述MinimumDiscountHelper 的其他行为。
[TestClass ] public class UnitTest1 { private IDiscountHelper getTestObject ( ) { return new MinimumDiscountHelper(); } [TestMethod ] public void Discount_Above_100 ( ) { IDiscountHelper target = getTestObject(); decimal total = 200 ; var discountedTotal = target.ApplyDiscount(total); Assert.AreEqual(total * 0.9 M, discountedTotal); } [TestMethod ] public void Discount_Between_10_And_100 ( ) { IDiscountHelper target = getTestObject(); decimal TenDollarDiscount = target.ApplyDiscount(10 ); decimal HundredDollarDiscount = target.ApplyDiscount(100 ); decimal FiftyDollarDiscount = target.ApplyDiscount(50 ); Assert.AreEqual(5 , TenDollarDiscount, "$10 discount is wrong" ); Assert.AreEqual(95 , HundredDollarDiscount, "$100 discount is wrong" ); Assert.AreEqual(45 , FiftyDollarDiscount, "$50 discount is wrong" ); } [TestMethod ] public void Discount_less_Than_10 ( ) { IDiscountHelper target = getTestObject(); decimal discount5 = target.ApplyDiscount(5 ); decimal discount0 = target.ApplyDiscount(0 ); Assert.AreEqual(5 , discount5); Assert.AreEqual(0 , discount0); } [TestMethod ] [ExpectedException(typeof(ArgumentOutOfRangeException)) ] public void Discount_Negative_Total ( ) { IDiscountHelper target = getTestObject(); target.ApplyDiscount(-1 ); } }
运行单元测试(并失败) 点击VS菜单栏测试 ,选择测试资源管理器 ,在窗口左上角点击在视图中运行所有测试 。
结果都是失败 的,因为所测试的这些方法还没有实现 。
实现特性 实现MinimumDiscountHelper 类的功能。
public decimal ApplyDiscount (decimal totalParam ) { if (totalParam < 0 ) { throw new ArgumentOutOfRangeException(); } else if (totalParam > 100 ) { return totalParam * .09 M; } else if (totalParam > 10 && totalParam <= 100 ) { return totalParam - 5 ; } else return totalParam; }
测试并修正代码 实现完功能后,运行所有测试 。
结果显示,3个单元测试通过,但是Discount_Between_10_And_100 失败。
原来问题出现在10 的边界值 上。只需要在if语句添加= ,包含恰好为10的值即可。
再次运行所有测试,全部通过。
使用Moq库 在实际的项目中,往往需要测试一些不能孤立运行的对象 。
一个有用的办法是使用模仿对象 ,它能够以一种特殊而受控的方式,来模拟 项目中实际对象 的功能。
右键EssentialTools.Tests 项目,添加新项,选择基本单元测试 ,文件名为UnitTest2.cs 。
修改UnitTest2.cs 文件,添加一个用于ShoppingCart类 的单元测试。
[TestClass ] public class UnitTest2 { private Product[] products = { new Product { Name = "Kayak" , Category = "Watersports" , Price = 275 M }, new Product { Name = "Lifejacket" , Category = "Watersports" , Price = 48.95 M }, new Product { Name = "Soccer ball" , Category = "Soccer" , Price = 19.50 M }, new Product { Name = "Corner flag" , Category = "Soccer" , Price = 34.95 M } }; [TestMethod ] public void Sum_Products_Correctly ( ) { var discounter = new MinimumDiscountHelper(); var target = new LinqValueCalculator(discounter); var goalTotal = products.Sum(e => e.Price); var result = target.ValueProducts(products); Assert.AreEqual(goalTotal, result); } }
要面临的问题是,LinqValueCalculator类依赖于IDiscountHelper接口的实现才能进行操作。即:
单元测试变得复杂和脆弱 。一旦该实现中的折扣逻辑发生变化 ,即使LinqValueCalculator可以正常工作,测试仍会失败 。
当单元测试失败时,不易知道问题是出在LinqValueCalculator类中,还是MinimumDiscountHelper类中。
将Moq添加到VS项目中 打开Nuget控制台输入以下命令:Install-Packet Moq -version 4.1.1309.1617 -projectname EssentialTools.Tests
projectname参数 告诉Nuget,希望把Moq包安装到单元测试项目中,而不是安装到主应用程序中。
对单元测试添加模仿对象 对单元测试添加模仿对象,即告诉Moq,使用哪一种对象,对它的行为进行配置,然后将该对象运用于测试目标。
在UnitTest2.cs 的单元测试中,添加测试方法Sum_Products_Correctly ,使用Mock对象 。
[TestMethod ] public void Sum_Products_Correctly ( ) { Mock<IDiscountHelper> mock = new Mock<IDiscountHelper>(); mock.Setup(m => m.ApplyDiscount(It.IsAny<decimal >())).Returns<decimal >(total => total); var target = new LinqValueCalculator(mock.Object); var result = target.ValueProducts(products); Assert.AreEqual(products.Sum(e => e.Price), result); }
注意 :
使用Mock<T> mock = new Mock<T>(); 语句,创建模仿对象,并指定想要测试的接口T。
用Setup方法 ,给模仿对象添加一个方法(可用LINQ或lambda表达式 ),传递要求它实现的接口(ApplyDiscount )。
It类 定义了许多以泛型类型参数 进行使用的方法。本例用decimal 调用了IsAny方法 ,当以任何十进制值为参数来调用ApplyDiscount 方法时生效。
Returns方法 指定在调用模仿方法时Moq要返回的结果 。类型参数 用以指定结果的类型(decimal) ,lambda表达式 指定结果 。
通过读取Mock<IDiscountHelper>对象的Object属性值 来使用模仿对象。即mock.Object 。
下表展示了It类的静态方法。
方法
描述
Is<T>(predicate)
指定类型T的值,该值使“predicate(谓词)”返回true
IsAny<T>()
指定类型T的值为任意值
IsInRange<T>(min, max, kind)
如果参数介于定义值之间,而且是T类型的,则匹配。最后一个参数(kind)是该范围 的一个枚举值,可以是Inclusive(包括)或Exclusive(排除)
IsRegex(expr)
如果参数匹配指定的正则表达式,则匹配一个字符串参数
用Moq创建模仿对象的过程包括几个步骤:
用Mock创建模仿对象。
用Setup方法建立模仿对象的行为。
用It类设置行为的参数。
用Returns方法指定行为的返回值。
用lambda表达式在Returns方法中建立具体行为。
使用Moq的好处是,单元测试只检查LinqValueCalculator对象的行为 ,并不依赖于 任何Models文件夹中的IDiscountHelper接口的实现 。即,当测试失败时,可以知道问题出在LinqValueCalculator实现中,或者是建立模仿对象的方式中。
创建更复杂的模仿对象 在UnitTest2.cs文件 中,添加一个新的测试方法Pass_Through_Variable_Discounts ,模仿更加复杂的IDiscountHelper接口 实现。
[TestMethod ] [ExpectedException(typeof(System.ArgumentOutOfRangeException)) ] public void Pass_Through_Variable_Discounts ( ) { 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.9 M)); mock.Setup(m => m.ApplyDiscount(It.IsInRange<decimal >(10 , 100 , Range.Inclusive))).Returns<decimal >(total => total - 5 ); var target = new LinqValueCalculator(mock.Object); 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.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 )); }
注意 :
调用Setup方法的顺序 会影响 模仿对象的行为,Moq会以相反的顺序评估给定的行为,必须按照从最一般得到最特殊 的顺序。
It.Is方法 是为不同参数值建立指定行为 最灵活的方式,可以用任何谓词 来返回true或false 。(模仿特定值)
It.IsInRange方法 能够捕捉参数值的范围 。(模仿值范围)