单元测试_使用 Nmock 单元测试 .NET 业务对象

使用Nmock单元测试 .NET 业务对象

Nmock是在开发环境下测试复杂业务对象的唯一方式

理解单元测试问题

在一个测试驱动的开发环境(test-driven developmentenvironment)下,为复杂的业务对象编写单元测试脚本很困难,因为,业务对象可能外部依赖(高耦合,high coupling)很多其他对象。

有时,对于有限的项目预算来说,建立开发环境,并配置它们的单元测试,不大可能。但是开发人员可以通过 mock 测试对象解决这个问题。

通过创建 mock 业务对象,而不是你复杂业务对象的真正实现,你可以一次测试一个类。Mock 对象是模拟对象,复制你的业务对象的行为,你可以在你真正的单元测试脚本中使用 这些mock 对象。

使用 Nmock

很少有微软 .NET 支持的模拟(mock)测试框架——Nmock 就是其中之一。Nmock 也可以很容易与 Nunit 测试框架集成。Nmock 是免费的。

Nmock是一个独特的具有以下特征的模拟测试库:

  • 同其他单元测试框架一样,开发人员需要定义期望。你期待什么作为输出
  • 模拟对象基于实现的接口,本质上是动态的。
  • 如果实际的输出与预期不符合,那么你的测试用例将失败。
  • 错误信息显示错误的详细描述。

安装 Nmock 测试环境

为了开发示例应用程序,使用 VisualStudio 2008 .NET 框架3.5。单元测试使用 NUnit 2.5.10。在安装NUnit msi包之后,除了能看到 nunit.framework.dll的引用,还能看到nunit.mocks.dll,但本示例项目中只引用 nunit.framework.dll

下载 Nmock 库,解压后,会得到一些 DLL 文件。在本示例项目中,只增加 NMock2.dll 的引用。本例演示常见的长度和距离的转换系数,将一个长度计量单位转换到另一个的转换系数。例如,米到英尺的转换系数是3.28

首先创建一个C#控制台程序。新建接口 IconversionService,代码如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:   
   5:  namespace MockTesting.Sample
   6:      {
   7:      public interface IconversionService
   8:          {
   9:          double GetConversionRate ( string fromlength, string tolength );
  10:          }
  11:   
  12:      }

说明:GetConversionRate 方法返回转换因子。

新建类 ConversionServices,代码如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:   
   5:  namespace MockTesting.Sample 
   6:      {
   7:      public class ConversionServices : IconversionService
   8:          {
   9:      
  10:         public double GetConversionRate ( string fromlength, string tolength )
  11:              {
  12:              if ( fromlength.Length <=0 || tolength.Length <= 0 )
  13:                  return 0;
  14:   
  15:                switch(fromlength)       
  16:                {         
  17:                   case "MET":   
  18:                      if (tolength.Trim().ToString() == "FT" )
  19:                          return 3.28;
  20:                      break;                  
  21:                   case "FT":            
  22:                         if (tolength.Trim().ToString() == "MET" )
  23:                             return 0.3048;
  24:                       break;
  25:   
  26:                   case "MILE":
  27:                       if ( tolength.Trim ( ).ToString ( ) == "KM" )
  28:                           return 1.609344;
  29:                       break;
  30:   
  31:                   case "KM":
  32:                       if ( tolength.Trim ( ).ToString ( ) == "MILE" )
  33:                           return 0.621371192;
  34:                       break;
  35:                   default:            
  36:                      return 0;         
  37:                            
  38:                 }
  39:                return 0;
  40:              }
  41:         
  42:          }
  43:      }

新建ConvertTotalValue 类,代码如下:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:   
   5:  namespace MockTesting.Sample
   6:      {
   7:      public class ConvertTotalValue
   8:          {
   9:          private readonly IconversionService conversionService;
  10:   
  11:          public double ConvertTotal ( string fromlength, string tolength, double totvalue )
  12:              {
  13:              if ( fromlength.Trim ( ).Length <=0 || tolength.Trim ( ).Length <=0 )
  14:                  return 0;
  15:              if ( totvalue == 0 )
  16:                  return 0;
  17:              return conversionService.GetConversionRate ( fromlength, tolength ) * totvalue;
  18:              }
  19:   
  20:          public ConvertTotalValue ( IconversionService conversionService )
  21:              {
  22:                  this.conversionService = conversionService;
  23:              }
  24:          }
  25:      }

说明:ConvertTotalValue 类里有一个只读的实例 IconversionService 接口,在其构造函数里实例化。那么这个实例就可以看作是一个 mock

使用Nmock库单元测试 .NET 业务对象(比较)

下面,为了演示一般的单元测试(使用 NUnit 库)与模拟测试(使用 Nmock库 )的区别,在 ConversionServicesTest  类创建两个不同的测试方法。

ShowCorrectLengthConversion 方法中,使用一个 ConversionServices类的对象,并调用GetConversionRate 方法获得转换系数。如果你 debug 一下 GetConversionRate方法,那么会发现程序的控制被转交给重构的GetConversionRate 函数。

而在ShowCorrectLengthConversionWithMock 方法中,没有创建任何 ConversionServices类的实例获得转换系数。取而代之的是真正的实现,创建一个 IconversionService 接口的mock实例,用来在计算转换的值。通过Nmock库函数创建一个模拟(mock)对象。请参考下面的代码:

   1:  Expect.Once.On ( mockconversionService ).
   2:    Method ( "GetConversionRate" ).
   3:    With ( objLengthType.FromLength, objLengthType.Tolength ).
   4:    Will ( Return.Value ( 3.28 ) );

Expect.Once.On将创建一个模拟(mock)实例。所以,调用参数为“MET”和“FT”的GetConversionRate方法时,会返回转换系数3.28。下面的代码显示完整的模拟测试代码:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:   
   5:  using NUnit.Framework;
   6:  using NMock2;
   7:   
   8:  namespace MockTesting.Sample
   9:      {
  10:      [TestFixture]
  11:      public class ConversionServicesTest
  12:   
  13:          {
  14:          private Mockery mocks ;
  15:   
  16:          private IconversionService mockconversionService ;
  17:   
  18:          private ConversionServices objConversionServices = new
  19:   ConversionServices ( );
  20:          private ConvertTotalValue objConvertTotalValue;
  21:   
  22:          [SetUp]
  23:          public void SetUp ( )
  24:   
  25:              {
  26:   
  27:              mocks = new Mockery ( );
  28:   
  29:              mockconversionService = mocks.NewMock<IconversionService> ( );
  30:   
  31:              objConvertTotalValue = new ConvertTotalValue ( mockconversionService );
  32:   
  33:              }
  34:          [Test]
  35:   
  36:          public void ShowCorrectLengthConversion ( )
  37:              {
  38:       
  39:              LengthType objLengthType = new LengthType ( );
  40:              objLengthType.FromLength= "MET";
  41:              objLengthType.Tolength= "FT";
  42:              Assert.AreEqual ( objConversionServices.GetConversionRate ( 
  43:              objLengthType.FromLength, objLengthType.Tolength ), 3.28 );
  44:            
  45:              }
  46:          [Test]
  47:          public void ShowCorrectLengthConversionWithMock ( )
  48:              {
  49:                               
  50:               LengthType objLengthType = new LengthType ( );
  51:               objLengthType.FromLength= "MET";
  52:               objLengthType.Tolength= "FT";
  53:   
  54:               Expect.Once.On ( mockconversionService ).
  55:               Method ( "GetConversionRate" ).
  56:               With ( objLengthType.FromLength, objLengthType.Tolength ).
  57:               Will ( Return.Value ( 3.28 ) );
  58:   
  59:               Assert.AreEqual ( objConvertTotalValue.ConvertTotal ( objLengthType.FromLength,              
  60:               objLengthType.Tolength, 10.0 ), 32.8 );
  61:               mocks.VerifyAllExpectationsHaveBeenMet ( );
  62:   
  63:              }
  64:   
  65:          }
  66:      
  67:      }

参考资料

http://www.nmock.org/
http://www.devx.com/DevXNet/Article/45071
http://msdn.microsoft.com/en-us/magazine/cc163904.aspx 
posted @ 2011-06-03 14:39  船长&CAP  阅读(566)  评论(0编辑  收藏  举报
免费流量统计软件