• 什么是Mocking framework?它有什么用?(转)


     今天我想讲下关于mocking frameworks,并且解释下他为什么有用处。我将给你们展示用和不用mocking framework两种测试方法。

    假设我们已经有了一个Driver类:

    [csharp] view plain copy
     
    1. public class Driver  
    2. {  
    3.     private IVehicle vehicleToDrive;  
    4.   
    5.     public Driver(IVehicle vehicleToDrive)  
    6.     {  
    7.         this.vehicleToDrive = vehicleToDrive;  
    8.     }  
    9.   
    10.     public bool EvasiveManeuvers(bool alertOffendingDriver)  
    11.     {  
    12.         bool success = false;  
    13.         if (alertOffendingDriver)  
    14.             success = this.vehicleToDrive.ApplyBrakes() && this.vehicleToDrive.HonkHorn();  
    15.         else  
    16.             success = this.vehicleToDrive.ApplyBrakes();  
    17.   
    18.         return success;  
    19.     }  
    20. }  

    【注意】Driver类的构造函数依赖IVehicle接口!定义如下:

    [csharp] view plain copy
     
    1. public interface IVehicle  
    2. {  
    3.     ///<summary>  
    4.     ///Honks the vehicle's horn.  
    5.     ///</summary>  
    6.     ///<returns>  
    7.     ///True if the action was successful.  
    8.     ///</returns>  
    9.     bool HonkHorn();  
    10.   
    11.     ///<summary>  
    12.     ///Applies the vehicle's brakes.  
    13.    ///</summary>  
    14.     ///<returns>  
    15.     ///True if the action was successful.  
    16.     ///</returns>  
    17.     bool ApplyBrakes();  
    18. }  

      现在我们需要写2个单元测试来测试Driver。然而在我们写之前,必须能够通过Driver的构造函数才行,他依赖IVehicle接口。
    很多人认为只要随便实现IVehicle就可以了,这当然没问题。事实上,如果你完成接口IVechicle,你也不用写单元测试了。
    单元测试是孤立性的,测试Driver类并且还要实现一个完整IVehicle实现不是单元测试,那是整体封闭测试。如果你在测试时出错了,你不能确认是Driver出错了还是其他的类。

    我们现在搞一个假的IVehicle的实现来解决这个依赖问题。

    [csharp] view plain copy
     
    1. public class FakeVehicle : IVehicle  
    2. {  
    3.     public int CalledHonkHorn = 0;  
    4.     public int CalledApplyBrakes = 0;  
    5.   
    6.     public bool HonkHorn()  
    7.     {  
    8.         this.CalledHonkHorn++;  
    9.         return true;  
    10.     }  
    11.   
    12.     public bool ApplyBrakes()  
    13.     {  
    14.         this.CalledApplyBrakes++;  
    15.         return true;  
    16.     }  
    17. }  

    注意我们定义的两个int成员,在我们的单元测试中我们将用他们判断HonkHorn()和ApplyBrakes()的调用情况。

    现在我们可以写单元测试了,我们将测试两个行为:
    1、Can_Evade_Trouble
    2、Can_Evade_Trouble_And_Alert_Offending_Driver

    [csharp] view plain copy
     
    1. [TestMethod]  
    2. public void Can_Evade_Trouble()  
    3. {  
    4.     // Arrange (set up a scenario)  
    5.     FakeVehicle fakeVehicle = new FakeVehicle();  
    6.     Driver target = new Driver(fakeVehicle);  
    7.   
    8.     // Act (attempt the operation)  
    9.     bool success = target.EvasiveManeuvers(false);  
    10.   
    11.     // Assert (verify the result)  
    12.     Assert.IsTrue(success);  
    13.     Assert.IsTrue(fakeVehicle.CalledHonkHorn == 0);  
    14.     Assert.IsTrue(fakeVehicle.CalledApplyBrakes == 1);  
    15. }  
    16.   
    17. [TestMethod]  
    18. public void Can_Evade_Trouble_And_Alert_Offending_Driver()  
    19. {  
    20.     // Arrange (set up a scenario)  
    21.     FakeVehicle fakeVehicle = new FakeVehicle();  
    22.     Driver target = new Driver(fakeVehicle);  
    23.   
    24.     // Act (attempt the operation)  
    25.     bool success = target.EvasiveManeuvers(true);  
    26.   
    27.     // Assert (verify the result)  
    28.     Assert.IsTrue(success);  
    29.     Assert.IsTrue(fakeVehicle.CalledHonkHorn == 1);  
    30.     Assert.IsTrue(fakeVehicle.CalledApplyBrakes == 1);  
    31. }  

    OK,现在我们成功的通过单元测试。他们的EvasiveManeuvers()方法都返回true,并且IVechicle.ApplyBrakes()方法每次都被调用了,HonkHorn()方法第一个测试没有被调用,第二次调用了。
    注意,在真正的测试驱动开发 TDD(Test Driven Development)我们首先要写测试,然后才是写代码再测试,但我们没这么做。

    这是一个比较不太讨人喜欢的写测试的风格,为了通过Driver我们写了一个FakeVehicle,假如需要写很多这种类那就麻烦了。

    为了解决这种问题,Mocking Framework诞生了。

    我们现在用一个叫Moq的框架(https://code.google.com/p/moq/),他的语法独特,但用过之后你会感到写起来很流畅。只要把moq.dll添加到引用,然后using Moq就可以了。

    现在我们重写上面的测试代码,回头再解释它的牛逼的地方。

    [csharp] view plain copy
     
    1. [TestMethod]  
    2. public void Can_Evade_Trouble()  
    3. {  
    4.     // Arrange (set up a scenario)  
    5.     Mock<IVehicle> mock = new Mock<IVehicle>();  
    6.     mock.Setup(x => x.ApplyBrakes()).Returns(true);  
    7.     Driver target = new Driver(mock.Object);  
    8.   
    9.     // Act (attempt the operation)  
    10.     bool success = target.EvasiveManeuvers(false);  
    11.   
    12.     // Assert (verify the result)  
    13.     Assert.IsTrue(success);  
    14.     mock.Verify(x => x.HonkHorn(), Times.Never());  
    15.     mock.Verify(x => x.ApplyBrakes(), Times.Once());  
    16. }  
    17.   
    18. [TestMethod]  
    19. public void Can_Evade_Trouble_And_Alert_Offending_Driver()  
    20. {  
    21.     // Arrange (set up a scenario)  
    22.     Mock<IVehicle> mock = new Mock<IVehicle>();  
    23.     mock.Setup(x => x.HonkHorn()).Returns(true);  
    24.     mock.Setup(x => x.ApplyBrakes()).Returns(true);  
    25.     Driver target = new Driver(mock.Object);  
    26.   
    27.     // Act (attempt the operation)  
    28.     bool success = target.EvasiveManeuvers(true);  
    29.   
    30.     // Assert (verify the result)  
    31.     Assert.IsTrue(success);  
    32.     mock.Verify(x => x.HonkHorn(), Times.Once());  
    33.     mock.Verify(x => x.ApplyBrakes(), Times.Once());  
    34. }  

    不管你信不信,反正我们可以丢掉FakeVehicle类了。
    Moq动态的构造了接口的实现类,所有的成员默认的值都是其默认值。
    由于bool类型的默认值是false,所以HonkHorn()ApplyBrakes()在mock.Object实例中都将返回false,显然我希望返回true的,所以用Moq的Setup()方法来解决。

    Setup参数是一个lambda表达式,可以强类型的方式直接访问到其成员。例如

    [csharp] view plain copy
     
    1. mock.Setup(x=>x.HonkHorn().Returns(true));  

    如果不用lambda用字符串,类似mock.Setup("HonkHorn").Returns(true),这种方式比较丑,如果接口变化了这边就该报错了。
    moq用lambda就是保证所有的访问都是强类型的。

    另外如果你的方法接受一些参数比如string例如

    [csharp] view plain copy
     
    1. mock.Setup(x => x.HonkHorn("loudly");  

    如果这个值不是必须的(不是这个值就不能通过),那就可以用It类代替,他包含很多有用的方法。下面的例子就是接受任意字符串

    [csharp] view plain copy
     
    1. mock.Setup(x => x.HonkHorn(It.IsAny<string>())).Returns(true);  

    Moq可以让你创建任意类型T的Mock<T>实例,然后调用Setup()去设置属性或方法的返回值,随便什么值只要是为你达到测试的目的。加入你需要一个属性返回一个集合,你只需要定义好集合类,并且通过Setup()的Returns方法返回集合就行了。当这个mock.Object的集合属性被访问时就会返回你定义好的集合。
    记住:1、mock的对象不是你的测试,你mock的对象是让你能够通过他们进入你要测试的类/组件。
    另外注意 var mock = new Mock<T>。mock可不是T的实例,mock是Mock<T>的对象实例,T的实例是mock.Object。所以不要搞混了;实例化Driver时要用mock.Object。

    [csharp] view plain copy
     
    1. //Do this  
    2. Mock<IVehicle> mock = new Mock<IVehicle>();  
    3. Driver driver = new Driver(mock.Object);  
    4.   
    5. //Not this  
    6. Mock<IVehicle> mock = new Mock<IVehicle>();  
    7. Driver driver = new Driver(mock);  

    现在回过头看看我们的moq的单元测试,所有的结果都是对的。调用次数也是对的,moq能自动记录方法的调用次数,我们只需要调用mock.Verify()然后通过lambda表达式就能验证我们想要的次数是否正确。

    这里是最基础的moq用法,希望你现在能够明白mockingframework的用处并明白moq怎么完成工作的。

  • 相关阅读:
    微博回调接口
    vue获取微博授权URL
    生成微博授权URL接口
    微博账号注册
    微博三方登录原理讲解
    使用celery异步发送短信
    celery配置与基本使用
    友情链接
    Linux无线网络设置--wpa_supplicant的使用
    wpa_supplicant介绍与使用
  • 原文地址:https://www.cnblogs.com/lip-blog/p/7920428.html
Copyright © 2020-2023  润新知