.NET平台测试驱动开发模拟框架Moq简明教程(实例剖析)<转>

简介:
在前文中,我们仅仅对Moq模拟对象框架的特征及历史等作了简单介绍。在本文中,我们将结合实例对这个框架作更具体的分析。


一、可以使用Moq模拟哪些内容?


你可以针对接口和现有类来使用Moq创建模拟对象。当应用于类时,需要具备一定的条件:类不能是封闭类型的(sealed);而且,被模拟的方法必须标记为虚拟类型(virtual)的。你无法简单地模拟静态方法(但是你可以使用Adaptor模式来模拟一个静态方法)。其实,上面这些限制条件与你使用另一个模拟对象框架Rhino Mocks时是一致的。

MoqRhino Mocks在后台实现上都使用了代理类的技术。而且,更深入一步了解,这两个框架都派生自相同的Castle DynamicProxy代码基类。

二、使用Moq对方法和属性进行模拟


现在,不妨设想一下,你正在编写一个数据库驱动的Web应用程序-例如一个在线商店。在进行任何其他部分的编码之前,你想首先完成这个电子商店软件的业务逻辑部分的设计。特别是,在编写完你的业务逻辑组件之前,你根本不想投入任何精力去编写数据访问有关的组件。

上面这种情形下特别适合于使用一种模拟对象框架。此时,你可以创建一个接口,用于描述你想使你的数据访问组件看上去的样子。然后,你便可以简单地模拟此接口,并且在测试你的业务逻辑时充分地利用模拟对象的优势(即不需要真正地实现被模拟的组件)。因此,借助于这种模拟方案,你可以先不考虑这些组件有关的编码,直到你已经为这部分被模拟组件的编程作好了充分的准备。

列表1中提供的第一个接口名字为IProductRepository,此描述共描述了两个方法。其中,第一个方法Select()负责返回数据库中所有的产品。第二个方法Get()根据给定的特定产品ID返回一个产品。此外,列表1还提供了一个接口,名字为IProduct;此接口用于描述某一种特定的产品。

列表1–IProductRepository.cs

   using System;

   using System.Collections.Generic;

   

   namespace MoqSamples.Models

   {

       public interface IProductRepository

    {

           List<IProduct> Select();

           IProduct Get(int id);

     }

  

     public interface IProduct

     {

         int Id {get; set;}

         string Name { get; set; }

     }

 }

让我们首先来考虑模拟IProduct接口。下列代码将创建一个模拟的Product对象,此对象拥有一个Id属性且其属性值为1,还有一个Name属性且其属性值为“Bushmills”

   //模拟一个Product对象

   var newProduct = new Mock<IProduct>();

   newProduct.ExpectGet(p => p.Id).Returns(1);

   newProduct.ExpectGet(p => p.Name).Returns("Bushmills");

上面的第一行代码从接口IProduct创建一个模拟。注意:这里所使用的Mock类是Moq框架所提供的,此类有一个泛型构造器,它能够接受李创建的接口类型。

接下来,建立了Id属性并且使之返回值1,又建立了Name属性并且使之返回值“Bushmills”。注意,此处是如何使用lambda表达式来描述IdName两个属性的。使用lambda表达式而不是使用字符串来描述一个属性的好处在于,支持例如像Resharper这样的重构工具能够对属性进行自动重构。

在上面创建完模拟对象newProduct后,接下来,你就可以像真正实现了接口IProduct一样来使用此模拟对象。例如,下面的断言(Assert)将会成功执行:

Assert.AreEqual("Bushmills", newProduct.Object.Name);

【注意】在此,当你引用模拟对象newProduct时必须使用newProduct.Object。这是因为newProduct变量代表的是代理类,而newProduct.Object变量代表的是实际的newProduct类。

接下来,让我们继续模拟IProductRepository接口。下列代码行创建了一个模拟对象IProductRepository,当调用它的Get()方法时它能够返回newProduct

   //模拟ProductRepository接口

   var productRepository = new Mock<IProductRepository>();

   productRepository

      .Expect(p => p.Get(1))

      .Returns(newProduct.Object);

上面的第一行代码通过把接口IProductRepository传递给类Mock的泛型构造器创建相应的模拟对象。接下来,第二行代码建立Get()方法以返回模拟对象newProduct。请再次注意,这里也使用了lambda表达式来描述方法Get()。当创建模拟对象IProductRepository完毕,你就可以在你的测试代码中像下面这样来使用它了:

   // Act

   

   var productReturned = productRepository.Object.Get(1);

   

   // Assert

   

   Assert.AreEqual("Bushmills", productReturned.Name);

   

在上面代码中,当你使用值1调用方法Get()时,返回的是newProduct对象。如果你调用除了1以外的其他值来调用Get()方法,那么将返回一个Null值。如果你想使得Get()方法返回newProduct,而不管传递给它是什么参数,那么你可以通过使用下列代码来创建模拟对象的方式实现:

   //模拟ProductRepository接口

   var productRepository = new Mock<IProductRepository>();

   productRepository

     .Expect(p => p.Get(It.IsAny<int>()))

     .Returns(newProduct.Object);

请注意,这里当建立方法的期望时我们把约束It.IsAny<int>()传递给方法Get()。这个参数可以使模拟的Get()方法访问任何整数值(integer)并返回newProduct对象。

另外,借助于lambda表达式的帮助,你甚至可以指定你所能够想到的任何定制约束。请参考如下代码片断:

   //模拟ProductRepository接口

   var productRepository = new Mock<IProductRepository>();

   productRepository

      .Expect(p => p.Get(It.Is<int>(id => id>0 && id<6)))

      .Returns(newProduct.Object);

在上面的代码中,仅当传递给Get()方法的参数介于值06之间时才返回一个newProduct对象。显然,这里的约束条件是通过把一个lambda表达式传递给方法It.Is()实现的。

三、使用Moq的行为校验支持


Moq框架可用于执行有限的行为校验功能。例如,你可以使用Moq来检测在调用结束另一个方法之后是否至少调用了某一特定的方法一次。

注意:Moq对于行为校验功能的支持也是有限的。不像其他的模拟对象框架,例如Rhino MocksTypemock Isolator,你不能够使用Moq来对象之间的测试复杂的交互。Moq并没有实现像Rhino MocksTypemock Isolator所提供的对于同一类型的代码记录(record)与回放(replay)功能的支持。

那么,你该在何时使用行为校验呢?为此,Martin Fowler提供了一个内存缓存的例子。他认为,一个缓存的关键特征在于你无法从其状态中判断当前缓存是否在使用中(在未真正实现这部分编码及投入使用之前)。这种情形特别适合于行为校验-即使针对简单情形下的硬编码方案的测试驱动开发者来说也是这个结论。

现在,不妨设想你已经实现了一个类ProductRepository,此类包含一个名字为GetProduct()的方法,此方法用于产品检索。于是,这个方法首先会试图从缓存中取得商品对象。如果失败,那么此方法将继续努力从数据库中检索相应的产品。列表2给出了这个类相应的代码。

列表2–ProductRepository

   using System;

   using System.Web;

   

   namespace MoqSamples.Models

   {

       public class ProductRepository : IProductRepository

       {

           private ProductCache _cache;

   

          public ProductRepository(ProductCache cache)

          {

              _cache = cache;

          }

  

          public virtual IProduct GetProduct(int id)

          {

              var product = _cache.Get(id);

              if (product == null)

              {

                  product = this.Get(id);

                  _cache.Set(id, product);

              }

              return product;

          }

          

          public virtual IProduct Get(int id)

          {

              throw new NotImplementedException();

          }

  

          public System.Collections.Generic.List<IProduct> Select()

          {

              throw new NotImplementedException();

          }

  

      }

  }

在上面的代码中,通过依赖性注入方式,ProductCache对象被传递给ProductRepository类。在此,ProductRepositoryGetProduct()方法首先试图从缓存对象中取得一个Product对象。如果获取失败的话,它将调用Get()方法来从数据库中取得此Product对象。注意,在此我没有给出Get()方法的实现。其实,我们所关注的仅是如何模拟它,所以这就足够了。

此外,注意到ProductCache类是一个强类型包装类,它所包装的是ASP.NET中十分重要的System.Web.Caching.Cache对象。列表3提供了ProductCache的相应代码:

列表3–ProductCache.cs

  using System;

  using System.Web;

  

  namespace MoqSamples.Models

  {

      public class ProductCache

      {

          public virtual IProduct Get(int id)

          {

              return (IProduct)HttpContext.Current.Cache["product_" + id];

          }

  

          public virtual void Set(int id, IProduct product)

          {

              HttpCon

原文地址:http://space.itpub.net/?uid-14518332-action-viewspace-itemid-431739

 

 

本文转自温景良(Jason)博客园博客,原文链接:http://www.cnblogs.com/wenjl520/archive/2009/08/21/1551301.html,如需转载请自行联系原作者

 

相关文章
|
9天前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
20 7
|
9天前
|
Linux C# Android开发
一个开源、跨平台的.NET UI框架 - Avalonia UI
一个开源、跨平台的.NET UI框架 - Avalonia UI
|
9天前
|
机器学习/深度学习 人工智能 算法
ML.NET:一个.NET开源、免费、跨平台的机器学习框架
ML.NET:一个.NET开源、免费、跨平台的机器学习框架
|
9天前
|
SQL 关系型数据库 数据库
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
七天.NET 8操作SQLite入门到实战详细教程(选型、开发、发布、部署)
|
9天前
|
消息中间件 开发框架 前端开发
YuebonCore:基于.NET8开源、免费的权限管理及快速开发框架
YuebonCore:基于.NET8开源、免费的权限管理及快速开发框架
|
9天前
|
JSON 测试技术 C#
C#/.NET/.NET Core优秀项目框架推荐榜单
C#/.NET/.NET Core优秀项目框架推荐榜单
|
16天前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
54 0
|
16天前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
38 0
|
4天前
|
移动开发 JSON Java
Jmeter实现WebSocket协议的接口测试方法
WebSocket协议是HTML5的一种新协议,实现了浏览器与服务器之间的全双工通信。通过简单的握手动作,双方可直接传输数据。其优势包括极小的头部开销和服务器推送功能。使用JMeter进行WebSocket接口和性能测试时,需安装特定插件并配置相关参数,如服务器地址、端口号等,还可通过CSV文件实现参数化,以满足不同测试需求。
25 7
Jmeter实现WebSocket协议的接口测试方法
|
4天前
|
JSON 移动开发 监控
快速上手|HTTP 接口功能自动化测试
HTTP接口功能测试对于确保Web应用和H5应用的数据正确性至关重要。这类测试主要针对后台HTTP接口,通过构造不同参数输入值并获取JSON格式的输出结果来进行验证。HTTP协议基于TCP连接,包括请求与响应模式。请求由请求行、消息报头和请求正文组成,响应则包含状态行、消息报头及响应正文。常用的请求方法有GET、POST等,而响应状态码如2xx代表成功。测试过程使用Python语言和pycurl模块调用接口,并通过断言机制比对实际与预期结果,确保功能正确性。
22 3
快速上手|HTTP 接口功能自动化测试