本文將介紹一款在.Net平臺下的Mock工具---Rhino Mocks 2,以及相關(guān)Mock工具的一些比較.在了解到Rhino Mocks 2之前我也接觸了一些其他的Mock工具, 比如EasyMock,JMock,NMock, NMock2,不過(guò)最終還是選擇了Rhino Mocks 2, 相信你在看完本文的介紹后會(huì )和我做出同樣的選擇 從一個(gè)例子說(shuō)起: 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 }
以上是一個(gè)Observer模式的小例子, 為了更加全面的體現出Mock對象的特性, 我在ISubscriber加了一個(gè)有返回值的方法MultiplyTwo, 它所做的事情就是將參數乘2并返回. 現在我們將對Publisher進(jìn)行測試, 然而Publisher中涉及到了另一個(gè)對象ISubscriber. 而我們暫時(shí)還沒(méi)實(shí)現ISubscriber , 所以我們將利用Mock Object來(lái)完成我的測試. 下面是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 }
大致看來(lái)NMock2和RhinoMocks2比較相像, 尤其在創(chuàng )建Mock對象的時(shí)候, 這點(diǎn)也是較之EasyMock和NMock比較合理的地方, 因為你只需一個(gè)MockRepository就可以創(chuàng )建出多個(gè)Mock Object, 并且可以直接獲得該類(lèi)型的對象, 不需要再用mock.GetMock().這樣的方法來(lái)獲得所需的Mock對象.并且它們都利用using block增加方便性.(在using block結束的時(shí)候會(huì )調用using對象的Dispose方法,此時(shí)將會(huì )自動(dòng)調用mocks.VerifyAll(), 不需要象EasyMock和NMock那樣顯式調用Verify方法.) 而NMock2和RhinoMocks2的區別主要在于Expectation階段. 僅僅從語(yǔ)法上來(lái)看, 你會(huì )發(fā)現它們都使用了Expectation的語(yǔ)法, 但是RhinoMocks2顯然更勝一籌. | void Receive(Object message);
| NMock2
| Expect.Once.On(subMock).Method("Receive").With(message);
| RhinoMocks2
| subMock.Receive(message);
|
RhinoMocks2的語(yǔ)法非常簡(jiǎn)潔也更加自然. 不過(guò)如果之前一直使用Expectation語(yǔ)法的可能會(huì )覺(jué)得奇怪, 怎么把方法的執行放到了Expectation階段. 注意到RhinoMocks2的這個(gè)特點(diǎn),你就會(huì )覺(jué)得很自然了, 對于沒(méi)有返回值的方法RhinoMocks2是這樣處理的. mock.Method(Parameter p); LastCall.On(mock); 不過(guò)LastCall.On(mock);可以被省略.RhinoMocks2會(huì )自動(dòng)為你補上. 再來(lái)看看對于有返回值的方法的處理: | 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);
|
簡(jiǎn)而言之,RhinoMocks2是類(lèi)型安全的. NMock2中使用的是字符串型的方法名,這樣既沒(méi)有了IDE自動(dòng)完成的支持,而且要在測試運行時(shí)才能檢查出錯誤. 并且對于參數和返回值的語(yǔ)法也是RhinoMocks2處理的更加簡(jiǎn)潔,自然. 為什么NMock2甚至JMock沒(méi)有使用類(lèi)型安全的語(yǔ)法? 不是它們沒(méi)有想到,而是由于它們和RhinoMocks2采取的實(shí)現模型是不一樣的. EasyMock.Net 和RhinoMocks2采用的是Record/Replay的模型,即先記錄Record將會(huì )發(fā)生的事, 然后在回放(Replay). 你可以看到RhinoMocks2的76行使用了mocks.ReplayAll(); 而NMock2并沒(méi)有調用該方法. NMock2和JMock都采用了Expectation的模型, 所有的期望發(fā)生的方法都使用Expect來(lái)定義.所以導致了它們之間的不同. 而RhinoMocks2就是結合兩者的優(yōu)點(diǎn)使得你既能獲得類(lèi)型安全又能使用類(lèi)似Expect的簡(jiǎn)潔語(yǔ)法. RhinoMocks2和NMock2相比較NMock都學(xué)習了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的缺點(diǎn): 對于所有的mock對象, 你必須預計(Expect)到它在執行時(shí)(excute)的所有將發(fā)生方法, 否則都會(huì )導致測試無(wú)法通過(guò), 唯一例外的一點(diǎn)就是NMock對此沒(méi)有要求.當你把NMockTest的第70行注釋掉,測試依然通過(guò). 不知道是不是因為這樣的原因RhinoMocks2并沒(méi)有提供Expect.Never.On這樣的語(yǔ)法. 也就是我無(wú)法保證某個(gè)方法不被調用. 而其他的框架都實(shí)現了類(lèi)似的功能. 我的期望: Expect.On(subMock).Call(subMock.MultiplyTwo(5)).Return(10); 其中On方法似乎顯的多余.不知道能不能改成下面這個(gè)樣子. Expect.Call(subMock.MultiplyTwo(5)).Return(10); 甚至于變成這樣 subMock.MultiplyTwo(5)==10;
當然Expectation的模型應該保留,因為對于某些需要指定異常,約束的情況你是無(wú)法通過(guò)subMock.MultiplyTwo(5)==10;這樣的形式來(lái)描述的. 雖然Expect的語(yǔ)法的語(yǔ)義比較強, 但是在書(shū)寫(xiě)的時(shí)候還是比較麻煩.不知道能不能有更好的模型. Mock對象在測試時(shí)無(wú)法調試, 這點(diǎn)和NUnit那樣基于狀態(tài)的測試相比差了點(diǎn), 只能靠自己動(dòng)腦子了. |