(一)次序(Ordered) 在rhinomock中,可以按次序调用方法。默认条件下,方法调用没有顺序。如果按次序录制,那么在调用方法时必须按录制时相同的次序进行。
请看: public interface ICustomer
{ string ShowTitle(string str); int Unid { get; set; } string CustomerName { get; set; } string Address { get; set; } }
测试: 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<string, string>>(); Expect.Call(oo("")).Return("abc"); mocks.ReplayAll(); Assert.AreEqual("abc", oo("")); }
再来一个例子: public class Customer
{ Func<string, string> _fun; public Customer(Func<string, string> 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<string, string>>(); 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("")); }
·Record,Using方式 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 { get; set; } string CustomerName { get; set; } string Address { get; set; } 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支持lambda和3.x扩展属性,所以在安装时可以这样: customer.Expect(p => p.OutParas("", out strOut)).Return("test").OutRef("xxx");
这个要结合Action<T>和Func<Tresult>来分析,尤其是Action<T>。 (三)方法选项 可以对安装期望的方法设置选项(options),例如:安装完期望的方法的可调用次数。 设置方法是:Expect.Call或LastCall 这里分别说一下: (1)Return 当然最长见的就是返回值,为读属性或带返回值的方法返回值设置,与期望匹配。 [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); }
(2)Throw 异常抛出。 [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> (1)StrictMock 通过这个方法可以创建一个具有严格语义的T类型mock对象,如果在使用过程中没有显式的对过程进行录制,则会出错误,并会抛出异常。 例如: [Test]
public void TestStrictMock() { MockRepository mocks = new MockRepository(); ICustomer customer = mocks.StrictMock<ICustomer>(); customer.Replay(); customer.ShowTitle(""); mocks.VerifyAll(); }
这里没有对customer的ShowTitle方法显式地安装期望, 而mock对象又是具有严格语义的对象,所以这里会发生错误,而抛出异常。 (2)DynamicMock 通过这个方法可以创建一个具有动态语义的T类型mock对象,如果在使用过种中没有显式的对过程进行录制,则不会出现异常。如果方法有返回值,那么会返回null或0。 同样以上个例子来说: public void TestDynamicMock()
{ MockRepository mocks = new MockRepository(); ICustomer customer = mocks.DynamicMock<ICustomer>(); customer.Replay(); customer.ShowTitle(""); mocks.VerifyAll(); }
这里同样没有进行显式的安装期望,但不会出现错误,不会抛出异常。所以当使用动态语义模拟对象时,没有显式安装期望的方法会被忽略。 (3)PartialMock 可以模拟类的一部分。可以独立测试抽象方法。它只能用于类。加一官方描述的话:如果方法上没有设置期望的值,就从一个调用类方法的默认类上去创建一个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(); }
(4)Stub 直接以例子进行 public interface IAnimal
{ int Legs { get; set; } int Eyes { get; set; } string Name { get; set; } string Species { get; set; } 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); }
从这段测试中可以看到,对customer的DescriptionId属性进行了结果安装,只让这个属性返回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 安装结果有两种方法: For和On,For在上边已经使用,On的参数是mock object。对于上边的示例中的粗体部分用On来实现为: SetupResult.On(customer).Call(customer.DescriptionId).Return(10);
这个也可以通过期望的选项来实现。例如: Expect.Call(customer.DescriptionId).Return(10)
.Repeat.Any() .IgnoreArguments(); 其中的粗体部分,可以多次使用,且忽略参数。 (二)约束(Constraints) 约束用来对期望的参数进行规则约束。系统提供了大量内建的约束方法,当然也可以自定义。这里直接贴一张官网给出的列表,一目了然:
例子: [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开头,且以!号结束。 |
|