清楚问题所在:

先开个头,当我们对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. 4th day

    老师讲的好快啊... /* 建一个innodb类型且字符集为utf8的表,其中包括以下类型的字段:int(自增长),float,char,varchar,datetime,date,text,enum ...

  2. 看小白如何解决ajax跨域问题

    由于此前很少写前端的代码(哈哈,不合格的程序员啊),最近项目中用到json作为系统间交互的手段,自然就伴随着众多ajax请求,随之而来的就是要解决ajax的跨域问题.本篇将讲述一个小白从遇到跨域不知道 ...

  3. js点击弹出div层

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. 使用Xcode8的Instruments检测解决iOS内存泄露(leak)

    在苹果没有出ARC(自动内存管理机制)时,我们几乎有一半的开发时间都耗费在这么管理内存上.后来苹果很人性的出了ARC,虽然在很大程度上,帮助我们开发者节省了精力和时间.但是我们在开发过程中,由于种种原 ...

  5. Tcpdump命令行 与 GUI Wireshark

    http://www.cnblogs.com/ggjucheng/archive/2012/01/14/2322659.html tcpdump host 192.168.1.26 and \(192 ...

  6. NSDateFormatter 格式说明

    格式化参数如下:    G: 公元时代,例如AD公元    yy: 年的后2位    yyyy: 完整年    MM: 月,显示为1-12    MMM: 月,显示为英文月份简写,如 Jan    M ...

  7. zoj 3537 Cake(区间dp)

    这道题目是经典的凸包的最优三角剖分,不过这个题目给的可能不是凸包,所以要提前判定一下是否为凸包,如果是凸包的话才能继续剖分,dp[i][j]表示已经排好序的凸包上的点i->j上被分割成一个个小三 ...

  8. oracle 自治事物 -- autonomous transaction

    一 使用规则 : 在begin 之前申明  : PRAGMA AUTONOMOUS_TRANSACTION; 二 使用理解:autonomous transaction 是一个独立的事务,这一点是理解 ...

  9. 3xian之所在

    最后一天,漫天飘起了雪花,假装欢送我离去. 这次WF之战不太顺利,早期的C题大概花了1秒钟构思,然而由于输出格式多了一个空格直到两个半小时才逃脱Wrong Answer的纠缠.还好lynncui在期间 ...

  10. BestCoder 1st Anniversary

    Souvenir  Accepts: 1078  Submissions: 2366  Time Limit: 2000/1000 MS (Java/Others)  Memory Limit: 26 ...