分享

RhinoMock框架模拟测试

 修行的嘟嘟 2011-06-24

(一)次序(Ordered

rhinomock中,可以按次序调用方法。默认条件下,方法调用没有顺序。如果按次序录制,那么在调用方法时必须按录制时相同的次序进行。

 

请看:

public interface ICustomer
{
   string ShowTitle(string str);
   int Unid { getset; }
   string CustomerName { getset; }
   string Address { getset; }
}

 

测试:

public void TestNoOrder()
{
    MockRepository mocks 
= new MockRepository(); 
    ICustomer customer 
= mocks.StrictMock<ICustomer>(); 

    
//默认条件下是没顺序的
    Expect.Call(customer.Unid).Return(1);
    Expect.Call(customer.CustomerName).Return(
"宋江");
    Expect.Call(customer.Address).Return(
"山东"); 

    mocks.ReplayAll(); 

    Assert.AreEqual(
"宋江", customer.CustomerName);
    Assert.AreEqual(
1, customer.Unid);
    Assert.AreEqual(
"山东", customer.Address);
}

 

 

当使用次序时:

public void TestOrder()
{
    MockRepository mocks 
= new MockRepository(); 
    ICustomer customer 
= mocks.StrictMock<ICustomer>();
    
using (mocks.Ordered())
    {
        Expect.Call(customer.Unid).Return(
1);
        Expect.Call(customer.CustomerName).Return(
"宋江");
        Expect.Call(customer.Address).Return(
"山东");
    } 

    mocks.ReplayAll();

    Assert.AreEqual(
"宋江", customer.CustomerName);
    Assert.AreEqual(
1, customer.Unid);
    Assert.AreEqual(
"山东", customer.Address);
}

 

这时,如果调用时没有按期望时的次序进行,那就会出错,抛出异常。

这种次序可以灵活使用,例如可以次序一个mock,然后期望条件达到后,再不按次序进行。注意:在进行回播前要退出次序。

 

(二)模拟委托

先定义委托:

public delegate void DoThing(string strMsg);

  

然后模拟委托:

[Test]
public void TestDelegate1()
{
    MockRepository mocks 
= new MockRepository();
    var oo 
= mocks.DynamicMock<DoThing>();
    oo(
"");
    mocks.ReplayAll();
    oo(
"");
    mocks.VerifyAll();
}

 

有两个系统定义的委托Func<TResult>Action<T>

前是带返回值的委托,后者不带返回值,现在通过Action<T>来实现上例

[Test]
public void TestDelegate2()
{
    MockRepository mocks 
= new MockRepository();
    var oo 
= mocks.DynamicMock<Action<string>>();

    oo(
"");
    mocks.ReplayAll();
    oo(
"");
    mocks.VerifyAll();
}

 

再来一个Func,即带返回值的委托的例子:

[Test]
public void TestDelegateFunc()
{
    MockRepository mocks 
= new MockRepository();
    var oo 
= mocks.DynamicMock<Func<stringstring>>();

    Expect.Call(oo(
"")).Return("abc");
    mocks.ReplayAll();
    Assert.AreEqual(
"abc", oo(""));
}

  

再来一个例子:

public class Customer
{
     Func
<stringstring> _fun;
     
public Customer(Func<stringstring> fun)
     {
          _fun 
= fun;
     }

     
public void DoSomething(string strMsg)
     {
          Console.WriteLine(_fun(strMsg));
     }
}

测试:

[Test]
public void TestDelegateFunc()
{
    MockRepository mocks 
= new MockRepository();
    var oo 
= mocks.DynamicMock<Func<stringstring>>();
    Expect.Call(oo(
"")).Return("abc");
    mocks.ReplayAll(); 

    var customer 
= new Customer(oo);
    customer.DoSomething(
"");
}
 

(一)MockRepository

RhinoMock的主要的类,是Mock仓库,用于创建mock对象,录制,重放,验证等。

1)创建Mock

MockRepository mock = new MockRepository();

 2)创建mock对象

Mock的目的就是创建mock对象,然后期望,录制重放等。创建mock对象有很多方法,以前的版本中通过: 

MockRepository fac = new MockRepository();
var customer 
= fac.CreateMock<ICustomer>();

来进行,CreateMock方法已经过时,工厂方法来进行:

MockRepository fac = new MockRepository();
var customer 
= fac.StrictMock<ICustomer>();

 

也可以通过静态工厂方法来创建:

var customer = MockRepository.GenerateMock<ICustomer>();

 

3)录制

所有的mock框架都使用Record/Replay模式,但有些是显示的,有些是隐式的,而RhinoMock就是显示的。

·Record/Replay传统的录制与重放:

[Test]
public void TestRR()
{

    MockRepository fac 
= new MockRepository();
    var customer 
= fac.StrictMock<ICustomer>();
 

    customer.Expect(p 
=> p.ShowTitle("")).Return("");
    customer.Replay();
    Assert.AreEqual(
"", customer.ShowTitle(""));
}

 

·RecordUsing方式

public void TestRR()
{
    MockRepository fac 
= new MockRepository();
    var customer 
= fac.StrictMock<ICustomer>();
    
using (fac.Record())
    {
       customer.Expect(p 
=> p.ShowTitle("")).Return("");
    }

    Assert.AreEqual(
"", customer.ShowTitle(""));
}

 

 

·通过静态工厂方法创建mock对象:

public void TestRR()
{
    var customer 
= MockRepository.GenerateMock<ICustomer>();
    customer.Expect(p 
=> p.ShowTitle("")).Return("");
    Assert.AreEqual(
"", customer.ShowTitle(""));
}

 

4)验证

用于验证mock对象的期望是否成立。

·Verify,用于验证mock对象的所有期望是否满足

·VerifyAll,用于验证所有mock对象的所有期望是否满足

 

(二)Expect

为有返回值的类的方法设置期望

Call:为方法或属性提供期望

·Call<T>(T ignored)

·Call(Action actionToExecute)

 

例:

[Test]
public void TestCall()
{
    MockRepository mock 
= new MockRepository();
    var mockObject 
= mock.StrictMock<ICustomer>();
    Expect.Call(mockObject.ShowTitle(
string.Empty)).Return("不能为空");

    Expect.Call(mockObject.Unid).Return(
30);
    mock.ReplayAll();
    Assert.AreEqual(
"不能为空", mockObject.ShowTitle(""));
    Assert.AreEqual(
30, mockObject.Unid);
}

 

 

lambda表达式方式

[Test]
public void TestCall()
{
    MockRepository mock 
= new MockRepository();
    var mockObject 
= mock.StrictMock<ICustomer>();
    mockObject.Expect(p 
=> p.ShowTitle("")).Return("不能为空");
    mockObject.Expect(p 
=> p.Unid).Return(30);

    mock.ReplayAll();
    Assert.AreEqual(
"不能为空", mockObject.ShowTitle(""));
    Assert.AreEqual(
30, mockObject.Unid);
}

 

 

再来看Using方式

[Test]
public void TestCallUsing()
{
    MockRepository mock 
= new MockRepository();
    var mockObject 
= mock.StrictMock<ICustomer>();

 

    
using (mock.Record())
    {
       mockObject.Expect(p 
=> p.ShowTitle("")).Return("不能为空");
       mockObject.Expect(p 
=> p.Unid).Return(30);
    }   

    Assert.AreEqual(
"不能为空", mockObject.ShowTitle(""));
    Assert.AreEqual(
30, mockObject.Unid);
}

 

(一)属性Property

属性也是一种方法。所以对于属性的期望,和方法是一样的。方法和属性的期望在前几篇随笔中已经大量使用。

通常的读或写属性器的期望

[Test]
public void TestEvent()
{
    MockRepository mocks 
= new MockRepository();
    IList
<int> _list = mocks.DynamicMock<IList<int>>();

    Expect.Call(_list.Count).Return(
5);
    mocks.ReplayAll();
    Assert.AreEqual(
5, _list.Count);
}

  

这个是很简单的。然后还有一种自动属性期望的,设置属性行为来达到自动属性期望安装。这个有两种方式,在前边说Mock四种类型时说过:

一是传统的一个一个的安装,还有一种方式就是通过Stub方式实现。

public interface ICustomer
{
    
int Unid { getset; }
    
string CustomerName { getset; }
    
string Address { getset; }
    
string ShowTitle(string str);
}

 

这个接口有3个属性。

[Test]
public void TestProperty()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>();
    
using (mocks.Record())
    {
         Expect.Call(customer.Unid).PropertyBehavior();
         Expect.Call(customer.CustomerName).PropertyBehavior();
         Expect.Call(customer.Address).PropertyBehavior();
    } 

    customer.Unid 
= 5;
    Assert.AreEqual(
5, customer.Unid);
}

 

通过这种方法要分别为mock对象设置属性行为。而通过Stub则很简单:

[Test]
public void TestPropertyStub()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.Stub<ICustomer>();   

    customer.Unid 
= 5;
    Assert.AreEqual(
5, customer.Unid);
}

 

通过PropertyBehavior()方法可以为属性模拟行为。这上行为会在模拟对象的生命周期内有效。模拟对象(mock object)与模拟(mock)是两个概念。 

(二)方法

属性器有读和写属性,在安装期望时,只是对写属性设置即可。在方法中有多种情况:

1)无返回值

void NoValues(string str);

 

安装期望:

Expect.Call(delegate { customer.NoValues(""); });

 

2)带返回值

这个前边已经大量使用

string ShowTitle(string str);

 

安装期望:

Expect.Call(customer.ShowTitle("")).Return("为空");

 3)带输出参数或引用参数的方法

[Test]
public void TestOutPara()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>();
    
string strOut="123";

    Expect.Call(customer
          .OutParas(
""out strOut))
          .Return(
"test").OutRef("xxx");

    mocks.ReplayAll();
    customer.OutParas(
""out strOut);
    Assert.AreEqual(
"xxx", strOut);
}

 

看粗体部分。带输出或引用参数的方法安装期望和正常调用的方式相似。

这里说明一下,RhinoMock3.5支持lambda3.x扩展属性,所以在安装时可以这样: 

customer.Expect(p => p.OutParas(""out strOut)).Return("test").OutRef("xxx");

 

这个要结合Action<T>Func<Tresult>来分析,尤其是Action<T> 

(三)方法选项

可以对安装期望的方法设置选项(options),例如:安装完期望的方法的可调用次数。

设置方法是:Expect.CallLastCall

这里分别说一下:

1Return

当然最长见的就是返回值,为读属性或带返回值的方法返回值设置,与期望匹配。 

[Test]
public void TestMethodOptions()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>();

    
using (mocks.Record())
    {
        Expect.Call(customer.Unid)
              .Return(
10);
    }

    Assert.AreEqual(
10, customer.Unid);
}

 

2Throw

异常抛出。

[Test]
public void TestMethodOptions()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>(); 

    
using (mocks.Record())
    {
         Expect.Call(customer.ShowTitle(
""))
               .Throw(
new Exception("不能为空"));
    }

    customer.ShowTitle(
""); 
}

 

结果:failed: System.Exception : 不能为空

3)方法允许使用的次数

[Test]
public void TestMethodOptions()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>(); 

    
using (mocks.Record())
    {
       Expect.Call(customer.ShowTitle(
""))
             .Return(
"不能为空")
             .Repeat.Once();
    } 

    Assert.AreEqual(
"不能为空",customer.ShowTitle(""));
    Assert.AreEqual(
"不能为空", customer.ShowTitle(""));
}

 

安装期望时,允许调用1次。所以对于两次断言使用,会有异常出现。除了Once,还有Twice()Any

4)忽略方法参数

[Test]
public void TestMethodOptions()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>();
    
using (mocks.Record())
    {
        Expect.Call(customer.ShowTitle(
null))
              .Return(
"不能为空")
              .IgnoreArguments();
    }

    Assert.AreEqual(
"不能为空", customer.ShowTitle(""));
}

 

请看粗体部分,忽略了字串参数。

5)简单的自动属性

PropertyBehavior()已经说过,不再赘述
 

MockRespository有四种泛型方法:

·CreateMock<T>

·CreateDynamicMock<T>

·PartialMock

·Stub

3.5中,三种已经过时的方法分别由以下方法替代:

·StrictMock<T>

·DynamicMock<T>

·PartialMock<T>

·Stub<T>

它们各自对应静态工厂方法:

·MockRepository.GenerateStrictMock<T>

·MockRepository.GenerateMock

·MockRepository.GeneratePartialMock<T>

·MockRepository.GenerateStub<T> 

 

1StrictMock

通过这个方法可以创建一个具有严格语义的T类型mock对象,如果在使用过程中没有显式的对过程进行录制,则会出错误,并会抛出异常。

例如:

[Test]
public void TestStrictMock()
{
    MockRepository mocks 
= new MockRepository();
    ICustomer customer 
= mocks.StrictMock<ICustomer>();

    customer.Replay();
    customer.ShowTitle(
"");
    mocks.VerifyAll();
}

  

这里没有对customerShowTitle方法显式地安装期望, mock对象又是具有严格语义的对象,所以这里会发生错误,而抛出异常。 

2DynamicMock

通过这个方法可以创建一个具有动态语义的T类型mock对象,如果在使用过种中没有显式的对过程进行录制,则不会出现异常。如果方法有返回值,那么会返回null0

同样以上个例子来说:

public void TestDynamicMock()
{
    MockRepository mocks 
= new MockRepository();
    ICustomer customer 
= mocks.DynamicMock<ICustomer>();

    customer.Replay();
    customer.ShowTitle(
"");
    mocks.VerifyAll();    
}

  

这里同样没有进行显式的安装期望,但不会出现错误,不会抛出异常。所以当使用动态语义模拟对象时,没有显式安装期望的方法会被忽略。 

3PartialMock

可以模拟类的一部分。可以独立测试抽象方法。它只能用于类。加一官方描述的话:如果方法上没有设置期望的值,就从一个调用类方法的默认类上去创建一个Mock对象。

现在用例子来说明一下,这个例子通过抽象类来进行,抽象类中有一模板方法,而其中的方法是个抽象的,这里通过官网提供的例子来进行:

 

public abstract class ProcessorBase
{
    
public int Register;
    
public virtual int Inc()
    {
      Register 
= Add(1);
      
return Register;
    }
    
public abstract int Add(int i);
}

  

[Test]
public void TestPartialMock()
{
    MockRepository mocks 
= new MockRepository();
    ProcessorBase proc 
= mocks.PartialMock<ProcessorBase>();
    
using (mocks.Record())
    {
        Expect.Call(proc.Add(
1)).Return(1);
        Expect.Call(proc.Add(
1)).Return(2);
    }   

    proc.Inc();
    Assert.AreEqual(
1, proc.Register);

    proc.Inc();
    Assert.AreEqual(
2, proc.Register);

    mocks.VerifyAll();
}

  

4Stub

直接以例子进行

public interface IAnimal
{
    
int Legs { getset; }
    
int Eyes { getset; }
    
string Name { getset; }
    
string Species { getset; }
    
event EventHandler Hungry;
    
string GetMood();
}

public class AnimalTest
{

   IAnimal _animal;
   
public AnimalTest(IAnimal animal)
   {
       _animal 
= animal;
   }

   
public void SetLegs(int count)
   {
       _animal.Legs 
= count;
   }
}

  

测试:

[Test]
public void CreateAnimalStub()
{

    MockRepository mocks 
= new MockRepository();
    IAnimal animal 
= mocks.DynamicMock<IAnimal>();

    Expect.Call(animal.Legs).PropertyBehavior();
    Expect.Call(animal.Eyes).PropertyBehavior();
    Expect.Call(animal.Name).PropertyBehavior();
    Expect.Call(animal.Species).PropertyBehavior(); 

    AnimalTest aa 
= new AnimalTest(animal);
    aa.SetLegs(
10);

    Assert.AreEqual(
10, animal.Legs);
}

  

设置接口属性行为,可以在实例中使用。这个属性行为可以通过Stub来设置,那就简单了:

[Test]
public void CreateAnimalStub()
{
    MockRepository mocks 
= new MockRepository();
    IAnimal animal 
= mocks.Stub<IAnimal>(); 

    AnimalTest aa 
= new AnimalTest(animal);
    aa.SetLegs(
10);
    Assert.AreEqual(
10, animal.Legs);
}

  

当然,也可利用反射来封装对象属性行为设置mock对象的所有属性:

public void SetPropertyBehaviorOnAllProperties(object mock)
{
    PropertyInfo[] properties 
= mock.GetType().GetProperties();
    
foreach (PropertyInfo property in properties)
    {
       
if(property.CanRead && property.CanWrite)
       {
          property.GetValue(mock, 
null);
          LastCall.On(mock).PropertyBehavior();
       }
    }
}

 

(一)安装结果(SetupResult

有时候在模拟对象中需要一个方法的返回值,而不在意这个方法是否被调用。就可以通过安装结果(SetupRestult)来设置返回值,而绕开期望安装,且可以使用多次。从依赖的角度来说是这样的:方法a(或属性)被方法b使用,而在其它的位置c处方法a又会被使用,而在c处使用之前,不保证是否在b处使用且修改了方法a的返回值。意思就是保证方法a的返回结果是固定的,是忽略它的依赖,而在它该用的位置使用它恒定的值。安装结果可以达到这种效果。 

public class Customer
{
    
public virtual int DescriptionId{get;set;}
    
public virtual void PrintDescription()
    {
        DescriptionId
=1;
    }
}

 

属性DesriptionId被方法PrintDescription()依赖。

 

[Test]
public void TestSetupResult()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<Customer>();
    SetupResult.For(customer.DescriptionId).Return(
10); 

    Expect.Call(
delegate { customer.PrintDescription(); }).Repeat.Times(2);

    mocks.ReplayAll();

    customer.PrintDescription();
    customer.PrintDescription();

    Assert.AreEqual(
10, customer.DescriptionId);
}

 

从这段测试中可以看到,对customerDescriptionId属性进行了结果安装,只让这个属性返回10。而在随后对依赖它的方法进行了期望安装,且可以被调用2次。但DescriptionId的值仍是10

Expect.Call(delegate { customer.PrintDescription(); }).Repeat.Times(2);

这句是对不带返回值的方法进行期望安装,当然可以使用Lambda来进行。这个匿名方法就是一个不带参数,没有返回值的委托,这个就相当于Action<>,通过lambda就是:()=>customer.PrintDescription(),完整就是:

Expect.Call(()=>customer.PrintDescription()).Repeat.Times(2);

关于匿名方法和Action<T>委托可见:

http://www.cnblogs.com/jams742003/archive/2009/10/31/1593393.html

http://www.cnblogs.com/jams742003/archive/2009/12/23/1630737.html

 

安装结果有两种方法:

ForOnFor在上边已经使用,On的参数是mock object。对于上边的示例中的粗体部分用On来实现为:

SetupResult.On(customer).Call(customer.DescriptionId).Return(10);

 

这个也可以通过期望的选项来实现。例如:

Expect.Call(customer.DescriptionId).Return(10)
      .Repeat.Any()
      .IgnoreArguments();

其中的粗体部分,可以多次使用,且忽略参数。

(二)约束(Constraints

约束用来对期望的参数进行规则约束。系统提供了大量内建的约束方法,当然也可以自定义。这里直接贴一张官网给出的列表,一目了然: 

约束

说明

例子

接受的值

拒绝的值

Is

任何

Is.Anything()

{0,"","whatever",null, etc}

Nothing Whatsoever

等于

Is.Equal(3)

3

5

不等于

Is.NotEqual(3)

null, "bar"

3

Is.Null()

null

5, new object()

不为无

Is.NotNull()

new object(), DateTime.Now

null

指定类型

Is.TypeOf(typeof(Customer))

or Is.TypeOf<Customer>()

myCustomer, new Customer()

null, "str"

大于

Is.GreaterThan(10)

15,53

2,10

大于等于

Is.GreaterThanOrEqual(10)

10,15,43

9,3

小于

Is.LessThan(10)

1,2,3,9

10,34

小于等于

Is.LessThanOrEqual(10)

10,9,2,0

34,53,99

匹配

Is.Matching(Predicate<T>)

 

 

相同

Is.Same(object)

 

 

不相同

Is.NotSame(object)

 

 

Property

等于值

Property.Value("Length",0)

new ArrayList()

"Hello", null

Property.IsNull

("InnerException")

new Exception

("exception without

 inner exception")

new Exception

("Exception

with inner Exception",

 new Exception("Inner")

不为无

Property.IsNotNull

("InnerException")

new Exception

("Exception with inner Exception",

new Exception("Inner")

new Exception

("exception without

inner exception")

List

集合中包含这个元素

List.IsIn(4)

new int[]{1,2,3,4},

 new int[]{4,5,6}

new object[]{"",3}

集合中的元素(去重)

List.OneOf(new int[]{3,4,5})

3,4,5

9,1,""

等于

List.Equal(new int[]{4,5,6})

new int[]{4,5,6},

new object[]{4,5,6}

new int[]{4,5,6,7}

Text

以…字串开始

Text.StartsWith("Hello")

"Hello, World",

"Hello, Rhino Mocks"

"", "Bye, Bye"

以…字串结束

Text.EndsWith("World")

"World",

"Champion Of The World"

"world", "World Seria"

包含

Text.Contains("or")

"The Horror Movie...",

"Either that or this"

"Movie Of The Year"

相似

Text.Like

("rhino|Rhinoceros|rhinoceros")

"Rhino Mocks",

"Red Rhinoceros"

"Hello world", "Foo bar",

Another boring example string"

 

例子:

[Test]
public void TestConstraints()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>();

    Expect.Call(customer.ShowTitle(
""))
          .Return(
"字符约束")
          .Constraints(Rhino.Mocks.Constraints
                           .Text.StartsWith(
"cnblogs")); 

    mocks.ReplayAll();
    Assert.AreEqual(
"字符约束", customer.ShowTitle("cnblogs my favoured"));
}

 

它的意思就是如果参数以cnblogs开头,则返回期望值。可以比较一下Moq的参数约束设置方法:

http://www.cnblogs.com/jams742003/archive/2010/03/02/1676197.html 

除了上述方法外,rhinomock约束还支持组合,即与,非,或。还以上例进行:

[Test]
public void TestConstraints()
{
    MockRepository mocks 
= new MockRepository();
    var customer 
= mocks.DynamicMock<ICustomer>();

    Expect.Call(customer.ShowTitle(
""))
          .Return(
"字符约束")
          .Constraints(
               Rhino.Mocks.Constraints.Text.StartsWith(
"cnblogs"
            
&& Rhino.Mocks.Constraints.Text.EndsWith("!")
    ); 

    mocks.ReplayAll();
    Assert.AreEqual(
"字符约束", customer.ShowTitle("cnblogs my favoured!"));
}

 

参数的条件就是以cnblogs开头,且以!号结束。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多