清楚问题所在:

先开个头,当我们对A进行单元测试时,可能会发现A的实现必须要依赖B。这时,我们在写单元测试时,就必须先创建B的实例,然后把B传给A再建立A的实例进行测试。

这样就会出现一些问题:

1、我们的单元测试会变得复杂而且脆弱。复杂是因为我们必须要花费精力去弄清楚B的逻辑。脆弱是因为如果B的逻辑更改了,我们对A的单元测试也可能会面临失败。

2、更严重的是,当我们测试失败时,我们无法很快定位到究竟是A除了问题还是B出了问题。

所以我们使用Moq这种技术来Mock “伪造” 一个B的实例,这样我们就能专注于对A的单元测试。

接下来开始记录一下使用Moq的案例。

首先了解一下不使用Moq的情况下我们怎么测试一个跟其他类有依赖关系的方法。

1、这里先声明了一个产品实体。其中有产品的名称、种类、和价格。

  public class Product
{
public string Name { set; get; }
public string Category { get; set; }
public decimal Price { set; get; }
}

2、我们有一个接口IValueCalculator,声明了一个方法来计算产品价格。

 public interface IValueCalculator
{
decimal ValueProducts(IEnumerable<Product> products);
}

3、还需要定义一个接口IDiscountHelper来给产品的价格打折。

 public interface IDiscountHelper
{
decimal GetDiscount(decimal price);
}

  有一个实现这个接口的MinDiscountHelper 类,根据不同的价格范围进行打折。

 public class MinDiscountHelper : IDiscountHelper
{ public decimal GetDiscount(decimal price)
{
if (price < )
{
throw new ArgumentOutOfRangeException();
}
else if (price > && price <= )
{
return price - ;
}
else if (price > )
{
return price * 0.9M;
}
else
{
return price;
}
}
}

4、接下来定义一个LinqValueCalculator 类来实现接口IValueCalculator。

  我们可以发现这个类要依赖于IDiscountHelper接口的实现来计算打折后的价格,然后实现IValueCalculator的ValueProducts()方法返回最终的产品价格。

 public class LinqValueCalculator : IValueCalculator
{
private IDiscountHelper discounter; public LinqValueCalculator(IDiscountHelper discountPara)
{
this.discounter = discountPara;
} public decimal ValueProducts(IEnumerable<Product> products)
{
return this.discounter.GetDiscount(products.Sum(p => p.Price));
}
}

5、如此一来,我们要测试LinqValueCalculator的方法时,就不得不先定义一个IDiscountHelper的实例。

这就会出现我们一开始所说的问题。

[TestClass]
public class UnitTest2 {
private Product[] products = {
      new Product {Name = "AAA", Price = 275M},
      new Product {Name = "BBB", Price = 48.95M},
      new Product {Name = "CCC", Price = 19.50M},
       new Product {Name = "DDD", Price = 34.95M}
    };

   [TestMethod]
  public void Sum_Products_Correctly() {
    // arrange
    var discounter = new MinimumDiscountHelper();
    var target = new LinqValueCalculator(discounter);
    var goalTotal = products.Sum(e => e.Price);
     // act
     var result = target.ValueProducts(products);
     // assert
     Assert.AreEqual(goalTotal, result);
  }
}

接下来我们使用Moq来解决这种问题,让我们可以专注于我们想要测试的模块。

1、在单元测试项目中打开NuGet程序包管理。

2、在右侧联机搜索Moq然后安装识别码为Moq的程序包即可。

3、可以看到Moq被引用到了单元测试项目里。

4、在测试类中引用命名空间。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

5、在测试方法中使用Moq。

 Mock<IDiscountHelper> mocker = new Mock<IDiscountHelper>();     // 创建Mock对象,伪造一个IDiscountHelper的实现

  先定义一个实现IDiscountHelper的Mock,这个Mock是一个实现了IDiscountHelper的杜撰实例。

 mocker.Setup(m => m.GetDiscount(It.IsAny<decimal>())).Returns<decimal>(total => total);     // 装载方法
mocker.Setup(m => m.GetDiscount(It.Is<decimal>(v => v == ))).Throws<ArgumentOutOfRangeException>(); // 参数等于0时,抛出异常
mocker.Setup(m => m.GetDiscount(It.Is<decimal>(v => v > ))).Returns<decimal>(total => total * 0.9M); // 参数大于100时,返回
mocker.Setup(m => m.GetDiscount(It.IsInRange<decimal>(, , Range.Inclusive))).Returns<decimal>(total => total - ); // 参数在10与100之间,包括10和100,返回-5

  使用Setup()来装载依赖的方法,用Returns<T>来返回任意类型的结果。

  在Setup()中使用lambda表达式,指定相应方法。用It对象来控制传入的参数,下面是It对象的一些常用方法:

  

  使用Returns()方法来控制返回值,同样支持lambda表达式。

    注意:Moq是以倒序的方式装载Setup()的,因此我们要最先写最基础的场景,往下写其他特殊的场景,确保所有场景都能够被覆盖。在这里,我们首先写了一个It.IsAny<decimal>来确保无论如何最终总能传入decimal参数,后面再根据不同的测试场景传入decimal参数。

  其实这个时候,我们已经跟之前定义的MinDiscountHelper类没什么关系了,我们直接使用Moq来做这个接口实现,返回数据给之后的测试。

  接着来我们只需要把实现了IDiscountHelper接口的Mock实例传给我们要测试的行为即可:

var test = new LinqValueCalculator(mocker.Object);

  整合起来如下:

        private Product[] InitProducts(decimal price)
{
return new Product[] { new Product { Price = price } };
} /// <summary>
/// 使用Moq辅助,单独测试跟其他模块有依赖关系的方法。
/// </summary>
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))] // 指定计划抛出的异常
public void TestMethod1()
{
Mock<IDiscountHelper> mocker = new Mock<IDiscountHelper>(); // 创建Mock对象,伪造一个IDiscountHelper的实现
/* 装载实现的GetDiscount方法。
* Mock的装载方式是倒序,因此要最先写最基础的场景,往下装载特殊的场景。
*/
mocker.Setup(m => m.GetDiscount(It.IsAny<decimal>())).Returns<decimal>(total => total); // 装载方法
mocker.Setup(m => m.GetDiscount(It.Is<decimal>(v => v == ))).Throws<ArgumentOutOfRangeException>(); // 参数等于0时,抛出异常
mocker.Setup(m => m.GetDiscount(It.Is<decimal>(v => v > ))).Returns<decimal>(total => total * 0.9M); // 参数大于100时,返回九折
mocker.Setup(m => m.GetDiscount(It.IsInRange<decimal>(, , Range.Inclusive))).Returns<decimal>(total => total - ); // 参数在10与100之间,包括10和100,返回-5 var test = new LinqValueCalculator(mocker.Object); //decimal zero = test.ValueProducts(InitProducts(0M));
decimal five = test.ValueProducts(InitProducts(5M));
decimal ten = test.ValueProducts(InitProducts(10M));
decimal fifty = test.ValueProducts(InitProducts(50M));
decimal hundred = test.ValueProducts(InitProducts(100M));
decimal twoHundred = test.ValueProducts(InitProducts(200M)); Assert.AreEqual(5M, five, "Test Five failed");
Assert.AreEqual(5M, ten, "Test Ten failed");
Assert.AreEqual(45M, fifty, "Test Fifty failed");
Assert.AreEqual(95M, hundred, "Test Hundred failed");
Assert.AreEqual( * 0.9M, twoHundred, "Test TwoHundred failed");
test.ValueProducts(InitProducts(0M));
}

  注意:我们还使用了 [ExpectedException(typeof(ArgumentOutOfRangeException))]  来捕获我们希望测试抛出的异常。

自此,Moq就解决了我们在开篇提到的问题,我们不用再关心所依赖的其他模块的具体实现,也不用担心它们是更改了。我们使用Moq杜撰那些依赖项,回传想要的数据给测试目标。这样我们就能心无旁骛地达到我们的测试目标。

【PRO ASP.NE MVC4 学习札记】使用Moq辅助进行单元测试的更多相关文章

  1. asp.net mvc4 学习笔记一(基本原理)

    做了8年的asp.net webform,用过MVVM但还没用过MVC , 虽然项目不用MVC,但是还是想了解一下,今天第二天学习,以下是学习心得. VS2012默认带有asp.net mvc3和as ...

  2. ASP.NET MVC4 学习记录

    之前在学习Artech的<ASP.NET MVC4框架揭秘>一书,学习过程中画了ASP.NET MVC4框架的草图,方便记忆.

  3. ASP.NET MVC4 学习系统一(项目模板)

    项目模板 1.空模板      空模板用于创建ASP.NETMVC 4网站的架构,包含基本的文件夹结构,以及需要引用的asp.netmvc程序集,也包含可能要使用的javaScript 库.模板同样包 ...

  4. ASP.NET MVC4学习笔记路由系统概念与应用篇

    一.概念 1.路由是计算机网络中的一个技术概念,表示把数据包从一个网段转发至另一网段.ASP.NET中的路由系统作用类似,其作用是把请求Url映射到相应的"资源"上,资源可以是一段 ...

  5. asp.net mvc4 学习1

    1 简介:微软在很早就看到了基于windows系统的web开发平台的需求,这时便开始提出自己的解决方案即微软的第一个基于web开发的平台ASP.再后来随着需求和性能的要求再2002年推出第二个解决方案 ...

  6. ASP.NET MVC4 学习系统五(Razor)

    Razor ,你好!       Razor 是一种把代码和内容进行平滑集成的语法.尽管它引入了一些新的符号和关键字,但是Razor并不是一种新的语法.相反,Razor允许用户使用已知的语言来编写代码 ...

  7. ASP.NET MVC4 学习系统四(视图)

    视图(Views)    在ASP.NET MVC框架中,想要返回给用户HTML的控制器操作,就要返回ActionResult类型的ViewResult实例,ActionResult知道如何渲染应答结 ...

  8. ASP.NET MVC4 学习系统三(控制器Controller)

    控制器(Controllers)    在MVC架构模式的上下文里,控制器响应用户的输入(比如,用户点击“保存”按钮),并协调模型.视图以及(经常)数据访问层.在ASP.NET MVC程序里,控制器就 ...

  9. ASP.NET MVC4学习笔记之Controller的激活

    一. 高层相关类说明 当路由系统根据请求Url收集路由信息后,下一步就要将路由信息传给Controller激活系统,Controller激活系统负责实现了IController接口的Controlle ...

随机推荐

  1. git上解决代码冲突

    1.切换到master: git co master 2.拉最新代码:git pull origin master 3.删掉多余符号 4.切换到提交的分支:git br Txxxx 5.合并:git  ...

  2. 2nd day

    <?php //求数组的平均值 $a3 = array( array(11,12, 13), array(21,22,23, 24, 25), array(31,32,33, 35), arra ...

  3. HTTP协议和WEB应用

    一.应用层协议原理 1.套接字(Socket):主机地址+端口地址.(通常为32位IP地址和16位端口号组成,总长度为48位) 2.进程通过套接字来接收和发送报文.因特网运输层将所提供的服务整合成两种 ...

  4. WCF:如何将net.tcp协议寄宿到IIS

    1 部署IIS 1.1 安装WAS IIS原本是不支持非HTTP协议的服务,为了让IIS支持net.tcp,必须先安装WAS(Windows Process Activation Service),即 ...

  5. Android获取cpu和内存信息、网址的代码

      android获取手机cpu并判断是单核还是多核 /** * Gets the number of cores available in this device, across all proce ...

  6. TCP/IP协议族-----10、搬家IP

  7. Linux Kernel: buffers和cached的区别

    The page cache caches pages of files to optimize file I/O. The buffer cache caches disk blocks to op ...

  8. TCP的封包与拆包

    对于基于TCP开发的通讯程序,有个很重要的问题需要解决,就是封包和拆包. 一.为什么基于TCP的通讯程序需要进行封包和拆包. TCP是个"流"协议,所谓流,就是没有界限的一串数据. ...

  9. iOS 实现时间线列表效果

    之前看到美团的订单详情页面很有特色,于是决定模仿一下这个效果. 其实就是简单的 TableView 技巧,下面我们就来一步一步实现它. 画个泡泡 首先到 Sketch 里画出气泡的效果 很简单,一个圆 ...

  10. ARM map(Program size)

    1.Keil程式编译完之后,在List目录下会生成一个.map文件,里面包含各个存储块数据大小. Code:ARM 指令. RO(Read only)只读数据,如const int gu8test = ...