Rhino Mocks 2 - Alpha Atom

来源:百度文库 编辑:神马文学网 时间:2024/06/13 11:47:52
Rhino Mocks 2
作者 idior
2005-08-07 14:46
本文将介绍一款在.Net平台下的Mock工具---Rhino Mocks 2,以及相关Mock工具的一些比较.在了解到Rhino Mocks 2之前我也接触了一些其他的Mock工具, 比如EasyMock,JMock,NMock, NMock2,不过最终还是选择了Rhino Mocks 2, 相信你在看完本文的介绍后会和我做出同样的选择
从一个例子说起:
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 subscribers = new List();
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来完成我的测试.
下面是4种Mock框架下的不同测试代码:
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 }
大致看来NMock2和RhinoMocks2比较相像, 尤其在创建Mock对象的时候, 这点也是较之EasyMock和NMock比较合理的地方, 因为你只需一个MockRepository就可以创建出多个Mock Object, 并且可以直接获得该类型的对象, 不需要再用mock.GetMock().这样的方法来获得所需的Mock对象.并且它们都利用using block增加方便性.(在using block结束的时候会调用using对象的Dispose方法,此时将会自动调用mocks.VerifyAll(), 不需要象EasyMock和NMock那样显式调用Verify方法.)
而NMock2和RhinoMocks2的区别主要在于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). 你可以看到RhinoMocks2的76行使用了mocks.ReplayAll(); 而NMock2并没有调用该方法. NMock2和JMock都采用了Expectation的模型, 所有的期望发生的方法都使用Expect来定义.所以导致了它们之间的不同. 而RhinoMocks2就是结合两者的优点使得你既能获得类型安全又能使用类似Expect的简洁语法.
RhinoMocks2和NMock2相比较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那样基于状态的测试相比差了点, 只能靠自己动脑子了.