• Rhino Mocks (RhinoMock)2


          本文将介绍一款在.Net平台下的Mock工具---Rhino Mocks 2,以及相关Mock工具的一些比较.在了解到Rhino Mocks 2之前我也接触了一些其他的Mock工具, 比如EasyMockJMockNMock NMock2,不过最终还是选择了Rhino Mocks 2, 相信你在看完本文的介绍后会和我做出同样的选择。(注: 本文不是Mock工具的入门文章,如果你之前尚未接触了解有关Mock对象,请先去了解相关资料)

    本文由于编写时间较早, 现在RhinoMock有了新的变化,请参考RhinoMock2 续一文.
          
    从一个例子说起:

       27   public interface ISubscriber

       28     {

       29         int MultiplyTwo(int i);

       30         void Receive(Object message);

       31     }

       32 

       33     public class Publisher

       34     {

       35         private List<ISubscriber> subscribers = new List<ISubscriber>();

       36 

       37         public void Add(ISubscriber s)

       38         {

       39             subscribers.Add(s);

       40         }

       41 

       42         public void Publish(object msg)

       43         {

       44             subscribers.ForEach(delegate(ISubscriber s) { s.Receive(msg); });

       45         }

       46         public int Caculate(int i)

       47         {

       48             int result = 0;

       49             subscribers.ForEach(delegate(ISubscriber s) { result += s.MultiplyTwo(i); });

       50             return result;

       51         }

       52     }

    以上是一个Observer模式的小例子, 为了更加全面的体现出Mock对象的特性, 我在ISubscriber加了一个有返回值的方法MultiplyTwo, 它所做的事情就是将参数乘2并返回.

    现在我们将对Publisher进行测试, 然而Publisher中涉及到了另一个对象ISubscriber. 而我们暂时还没实现ISubscriber , 所以我们将利用Mock Object来完成我的测试.

    下面是4Mock框架下的不同测试代码: 

    EasyMock.Net

       55 namespace EasyMockTest

       56 {

       57     [TestFixture]

       58     public class PublisherTest

       59     {

       60         [Test]

       61         public void OneSubscriberReceivesAMessage()

       62         {

       63             //setup

       64             MockControl  mockCtrl= MockControl.CreateControl(typeof(ISubscriber));

       65             ISubscriber subMock = mockCtrl.GetMock() as ISubscriber;

       66             Publisher publisher = new Publisher();

       67             publisher.Add(subMock);

       68             object message = new object();

       69 

       70             //record

       71             mockCtrl.Reset();

       72             subMock.Receive(message);

       73             subMock.MultiplyTwo(5);

       74             mockCtrl.SetReturnValue(10);

       75             mockCtrl.Replay();

       76 

       77             //execute

       78             publisher.Publish(message);

       79             Assert.AreEqual(10, publisher.Caculate(5));

       80 

       81             //verify

       82             mockCtrl.Verify();

       83         }

       84     }

       85 }

     

    NMock

       55 namespace NMockTest

       56 {

       57     [TestFixture]

       58     public class PublisherTest

       59     {

       60         [Test]

       61         public void OneSubscriberReceivesAMessage()

       62         {

       63             // set up

       64             Mock mockSubscriber = new DynamicMock(typeof(ISubscriber));

       65             Publisher publisher = new Publisher();

       66             publisher.Add((ISubscriber)mockSubscriber.MockInstance);

       67             object message = new Object();

       68 

       69             // expectations

       70             mockSubscriber.Expect("Receive", message);   //commentted is still ok

       71             mockSubscriber.ExpectAndReturn("MultiplyTwo", 10, 5);

       72 

       73             // execute

       74             publisher.Publish(message);

       75             Assert.AreEqual(10, publisher.Caculate(5));

       76 

       77             // verify

       78             mockSubscriber.Verify();

       79         }

       80     }

       81 }

     

    NMock2

       55 namespace NMock2Test

       56 {

       57     [TestFixture]

       58     public class PublisherTest

       59     {

       60         [Test]

       61         public void OneSubscriberReceivesAMessage()

       62         {

       63             using (Mockery mocks = new Mockery())

       64             {

       65                 //setup

       66                 ISubscriber subMock = mocks.NewMock(typeof(ISubscriber)) as ISubscriber;

       67                 Publisher publisher = new Publisher();

       68                 publisher.Add(subMock);

       69                 object message = new object();

       70 

       71                 //expectations

       72                 Expect.Once.On(subMock).Method("Receive").With(message);

       73                 Expect.Once.On(subMock).Method("MultiplyTwo").With(5).Will(Return.Value(10));

       74 

       75                 //excute

       76                 publisher.Publish(message);

       77                 Assert.AreEqual(10, publisher.Caculate(5));

       78             }// verify when mocks dispose

       79         }

       80     }

       81 } 

    RhinoMocks2

       55 namespace RhinoMocks2Test

       56 {

       57     [TestFixture]

       58     public class PublisherTest

       59     {

       60         [Test]

       61         public void OneSubscriberReceivesAMessage()

       62         {

       63             using (MockRepository mocks = new MockRepository())

       64             {

       65                 //setup

       66                 ISubscriber subMock = mocks.CreateMock(typeof(ISubscriber)) as ISubscriber;

       67                 Publisher publisher = new Publisher();

       68                 publisher.Add(subMock);

       69                 object message = new object();

       70 

       71                 //record with expectation model

       72                 subMock.Receive(message);

       73                 Expect.On(subMock).Call(subMock.MultiplyTwo(5)).Return(10);

       74 

       75                 //end record

       76                 mocks.ReplayAll();

       77 

       78                 //excute

       79                 publisher.Publish(message);

       80                 Assert.AreEqual(10, publisher.Caculate(5));

       81             }//verify when mocks dispose

       82         }

       83     }

       84 }

           大致看来NMock2RhinoMocks2比较相像, 尤其在创建Mock对象的时候, 这点也是较之EasyMockNMock比较合理的地方, 因为你只需一个MockRepository就可以创建出多个Mock Object, 并且可以直接获得该类型的对象, 不需要再用mock.GetMock().这样的方法来获得所需的Mock对象.并且它们都利用using block增加方便性.(using block结束的时候会调用using对象的Dispose方法,此时将会自动调用mocks.VerifyAll(), 不需要象EasyMockNMock那样显式调用Verify方法.)

    NMock2RhinoMocks2的区别主要在于Expectation阶段.

    仅仅从语法上来看, 你会发现它们都使用了Expectation的语法, 但是RhinoMocks2显然更胜一筹.                

     

    void Receive(Object message);

    NMock2

    Expect.Once.On(subMock).Method("Receive").With(message);

    RhinoMocks2

    subMock.Receive(message);

    RhinoMocks2的语法非常简洁也更加自然. 不过如果之前一直使用Expectation语法的可能会觉得奇怪, 怎么把方法的执行放到了Expectation阶段. 注意到RhinoMocks2的这个特点,你就会觉得很自然了, 对于没有返回值的方法RhinoMocks2是这样处理的.

    mock.Method(Parameter p);
           LastCall.On(mock);

    不过LastCall.On(mock);可以被省略.RhinoMocks2会自动为你补上.

    再来看看对于有返回值的方法的处理:

     

    int MultiplyTwo(int i);

    NMock2

    Expect.Once.On(subMock).Method("MultiplyTwo").With(5).Will(Return.Value(10));

    RhinoMocks2

    Expect.On(subMock).Call(subMock.MultiplyTwo(5)).Return(10);

    简而言之,RhinoMocks2是类型安全的. NMock2中使用的是字符串型的方法名,这样既没有了IDE自动完成的支持,而且要在测试运行时才能检查出错误. 并且对于参数和返回值的语法也是RhinoMocks2处理的更加简洁,自然.

    为什么NMock2甚至JMock没有使用类型安全的语法? 不是它们没有想到,而是由于它们和RhinoMocks2采取的实现模型是不一样的. EasyMock.Net RhinoMocks2采用的是Record/Replay的模型,即先记录Record将会发生的事, 然后在回放(Replay). 你可以看到RhinoMocks276行使用了mocks.ReplayAll(); NMock2并没有调用该方法. NMock2JMock都采用了Expectation的模型, 所有的期望发生的方法都使用Expect来定义.所以导致了它们之间的不同. RhinoMocks2就是结合两者的优点使得你既能获得类型安全又能使用类似Expect的简洁语法.

    RhinoMocks2NMock2相比较NMock都学习了JMock强大的Constraints.

    Constraint:

    Example:

    Object

    Anything

    Is.Anything()

    Equal

    Is.Equal(3)

    Not Equal

    Is.NotEqual(3) or !Is.Equal(3)

    Null

    Is.Null()

    Not Null

    Is.NotNull()

    Type Of

    Is.TypeOf(typeof(string))

    Greater Than

    Is.GreaterThan(10)

    Greater Than Or Equal

    Is.GreaterThanOrEqual(10)

    Less Than

    Is.LessThan(10)

    Less Than Or Eqaul

    Is.LessThanOrEqual(10)

    Property

    Equal To Value

    Property.Value("Length",0)

    Null

    Property.IsNull("InnerException")

    Not Null

    Property.IsNotNull("InnerException")

    List

    Is In List
    [the parameter is a collection that contains this value]

    List.IsIn(4)

    One Of
    [parameter equal to one of the objects in this list]

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

    Equal

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

    Text

    Starts With

    Text.StartsWith("Hello")

    Ends With

    Text.EndsWith("World")

    Contains

    Text.Contains("or")

    Like
    [perform regular expression validation]

    Text.Like("Rhino|rhino|Rhinoceros|rhinoceros" )

    Operator Overloading

    And - operator &

    Text.StartsWith("Hello") & Text.Contains("bar")

    Or - operator |

    Text.StartsWith("Hello") & Text.Contains("bar")

    Not - operator !

    !Text.StartsWith("Hello")


    RhinoMocks2
    的缺点:

    对于所有的mock对象, 你必须预计(Expect)到它在执行时(excute)的所有将发生方法, 否则都会导致测试无法通过, 唯一例外的一点就是NMock对此没有要求.当你把NMockTest的第70行注释掉,测试依然通过.

    不知道是不是因为这样的原因RhinoMocks2并没有提供Expect.Never.On这样的语法. 也就是我无法保证某个方法不被调用. 而其他的框架都实现了类似的功能. 

    我的期望:

    Expect.On(subMock).Call(subMock.MultiplyTwo(5)).Return(10);

    其中On方法似乎显的多余.不知道能不能改成下面这个样子.

    Expect.Call(subMock.MultiplyTwo(5)).Return(10);

    甚至于变成这样

    subMock.MultiplyTwo(5)==10;

    当然Expectation的模型应该保留,因为对于某些需要指定异常约束的情况你是无法通过subMock.MultiplyTwo(5)==10;这样的形式来描述的.

    虽然Expect的语法的语义比较强, 但是在书写的时候还是比较麻烦.不知道能不能有更好的模型.

    Mock对象在测试时无法调试, 这点和NUnit那样基于状态的测试相比差了点, 只能靠自己动脑子了.


    参考资料:   RhinoMocks2      Enterprise Test Driven Develop


    后记: 在询问过Rhino Mocks 2的作者Ayende Rahien之后,对于我的期望,他给了以下回复,看来在技术不太可行。
    --- Question
    hi, recently i came across RhinoMocks2.0. i have got a question
    Expect.On(mock).Call(mock.MethodA(1)).Return(2);
    why we still need On(Mock) here?
    and i want to know if we can let the expect model be like this.
    mock.MethodA(1)==2;
    ---
    --- Reply
    The On(mock) so the repository would know which mock object the call
    originated from, the Call() syntax is a merely a better syntax for this. The
    problem is that there really is no way to implement a way to detect what was
    the last call in a good way across multiple mock objects and multiply mock
    repositories.

    About the second question, currently I can think of no way to do that, since
    this would require cooperation from the compiler itself in order to discover
    what the value of 2 is.
    Consider the behind the scenes implications of this, you first record an
    action, and the return value is stored in anticipation for the action's
    replay.
    How would this syntax allow the framework to grab the return value and so
    return it when called ?
    ---
    ---Question
    I wonder why  don't u implement Expect.Never.On,or i haven't found it?
    NMock2 and JMock both have some similar function.
    ---
    ---Reply
    That is the default behavior.
    The idea is that /any/ call that was not explicitly setup as expected is not
    expected.

    You can do something similar by:

    Expect.On(mock).Call(mock.Method()).Repeat.Times(0,0);

    This is not neccecary, however and can cause problems if you're using
    Ordered behaviors.
    ---

  • 相关阅读:
    dom4j 解析 xml文件1
    java 简单的动态代理例子
    标识接口的作用 (转)
    JAVA servlet输出IE6下乱码
    java时间操作函数汇总
    IE支持getElementsByClassName方法
    女朋友问我 LB 是谁?
    人类高质量 Java 学习路线【一条龙版】
    程序员作图工具和技巧,你 get 了么?
    3 分钟了解 JSON Schema
  • 原文地址:https://www.cnblogs.com/idior/p/209351.html
Copyright © 2020-2023  润新知