nev*_*ame 884 testing mocking stub
我已经阅读过各种关于模拟和测试中存根的文章,包括Martin Fowler的Mocks Are Not Stubs,但仍然不明白其中的区别.
Rys*_*gan 813
对象有几种定义,不是真实的.一般术语是测试双倍.这个术语包括:虚拟,假,存根,模拟.
- 虚拟对象传递但从未实际使用过.通常它们仅用于填充参数列表.
 - 假对象实际上有工作实现,但通常需要一些使它们不适合生产的快捷方式(内存数据库就是一个很好的例子).
 - 存根提供了在测试期间进行的调用的固定答案,通常不会对测试中编程的任何内容做出任何响应.存根还可以记录有关呼叫的信息,例如记住它'发送'的消息的电子邮件网关存根,或者可能只记录它'发送'的消息.
 - 模拟是我们在这里所讨论的:预编程的对象具有预期,形成了预期接收的调用的规范.
 
Mocks vs Stubs =行为测试与状态测试
根据每次测试只测试一件事的原则,在一次测试中可能有几个存根,但通常只有一个模拟.
使用存根测试生命周期:
使用模拟测试生命周期:
模拟和存根测试都给出了问题的答案:结果是什么?
使用模拟测试也对以下方面感兴趣:结果如何实现?
Sea*_*ver 691
存根
我相信最大的区别是你已经用预定的行为编写了一个存根.所以你会有一个实现依赖的类(最有可能的抽象类或接口),你为了测试目的而伪装,这些方法只是用set响应来删除.他们不会做任何花哨的事情,你会在测试之外编写它的存根代码.
嘲笑
模拟是你的测试的一部分,你必须设置你的期望.模拟不是以预定方式设置的,因此您可以在测试中使用代码.某种方式的模拟是在运行时确定的,因为设置期望的代码必须在它们执行任何操作之前运行.
区别
用模拟编写的测试通常遵循initialize -> set expectations -> exercise -> verify测试模式.虽然预先编写的存根将遵循initialize -> exercise -> verify.
相似
两者的目的是消除测试类或函数的所有依赖关系,以便您的测试更加集中,更简单,他们试图证明.
Arn*_*psa 324
存根是简单的假物体.它只是确保测试顺利进行.
模拟是更聪明的存根.您验证您的测试通过它.
Lev*_*Lev 225
以下是对每个示例的描述,然后是真实世界的示例.
虚拟 - 只是虚假的价值来满足API.
示例:如果您正在测试一个类的方法,该类在构造函数中需要许多强制参数而对测试没有影响,那么您可以创建虚拟对象以创建类的新实例.
假 - 创建一个可能依赖于某些外部基础结构的类的测试实现.(这是很好的做法,你的单元测试也不要实际上外部基础设施交互.)
示例:创建用于访问数据库的虚假实现,将其替换为
in-memory集合.
存根 - 覆盖返回硬编码值的方法,也称为state-based.
示例:您的测试类取决于
Calculate()需要5分钟才能完成的方法.您可以使用返回硬编码值的存根替换其实际实现,而不是等待5分钟; 只占一小部分时间.
模拟 - 非常相似Stub但interaction-based不是基于状态.这意味着您不希望Mock返回某个值,而是假设方法调用的特定顺序.
示例:您正在测试用户注册类.打电话后
Save,应该打电话SendConfirmationEmail.
Stubs并且Mocks实际上是子类型Mock,包括交换实际实现和测试实现,但出于不同的,特定的原因.
小智 168
在codeschool.com课程中,Rails Testing for Zombies,他们给出了这些术语的定义:
存根
用于使用返回指定结果的代码替换方法.
嘲笑
一个断言,断言该方法被调用.
正如Sean Copenhaver在他的回答中所描述的那样,区别在于模拟设定了期望(即做出断言,关于是否或如何被调用).
Ghi*_*nio 33
我认为关于这个问题的最简单和更清晰的答案来自Roy Osherove在他的书"单位测试的艺术"(第85页)中
告诉我们处理存根的最简单方法是注意存根永远不会使测试失败.断言测试用途总是针对被测试的类.
另一方面,测试将使用模拟对象来验证测试是否失败.[...]
同样,模拟对象是我们用来查看测试是否失败的对象.
这意味着如果你正在对假冒进行断言,这意味着你使用假冒作为模拟,如果你只使用假冒运行测试而没有断言,你使用假冒作为存根.
O'R*_*ney 26
阅读上面的所有解释,让我试着浓缩:
Neb*_*Fox 22
模拟只是测试行为,确保调用某些方法.Stub是特定对象的可测试版本(本身).
你是什么意思Apple方式?
Joe*_*ang 19
我认为他们之间最重要的区别是他们的意图.
让我试着在WHY stub和WHY mock中解释它
假设我正在为我的mac twitter客户端的公共时间线控制器编写测试代码
这是测试示例代码
twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline
Run Code Online (Sandbox Code Playgroud)
通过编写mock,您可以通过验证期望得到满足来发现对象协作关系,而stub只模拟对象的行为.
如果你想了解更多关于模拟的信息,我建议你阅读这篇文章:http://jmock.org/oopsla2004.pdf
R01*_*010 17
要非常清楚和实用:
Stub:一个类或对象,它实现要伪造的类/对象的方法,并始终返回您想要的内容.
JavaScript中的示例:
var Stub = {
   method_a: function(param_a, param_b){
      return 'This is an static result';
   }
}
Run Code Online (Sandbox Code Playgroud)
模拟:存根相同,但它添加了一些逻辑,在调用方法时"验证",因此您可以确定某些实现正在调用该方法.
正如@mLevan所说,想象一下您正在测试用户注册类.调用Save后,应该调用SendConfirmationEmail.
一个非常愚蠢的代码示例:
var Mock = {
   calls: {
      method_a: 0
   }
   method_a: function(param_a, param_b){
     this.method_a++; 
     console.log('Mock.method_a its been called!');
   }
}
Run Code Online (Sandbox Code Playgroud)
        Mos*_*sho 16
使用心理模型确实帮助我理解了这一点,而不是所有的解释和文章,并没有完全"沉入".
想象一下,你的孩子桌子上有一个玻璃盘,他开始玩它.现在,你担心它会破裂.所以,你给他一块塑料板.这将是一个模拟(相同的行为,相同的界面,"更软"的实现).
现在,假设你没有塑料更换,所以你解释"如果你继续玩它,它会破裂!".这是一个Stub,你事先提供了一个预定义的状态.
一个假人将他甚至没有用叉子...和间谍可能是这样提供的工作你已经使用了相同的解释.一个假会像卧,如果他继续警察会来(不知道最后一部分:).
小智 15
模拟:帮助模拟和检查结果交互。这些交互是 SUT 对其依赖项进行调用以更改其状态。
存根:帮助模拟传入的交互。这些交互是 SUT 对其依赖项进行调用以获取输入数据。
来源:单元测试原则、实践和模式 - Manning
Rel*_*ros 11
一个假是可以用来描述任何存根或模拟对象(手写或其他方式),因为他们看起来像真正的对象的总称.
假的是存根还是模拟取决于它在当前测试中的使用方式.如果它用于检查交互(断言),则它是一个模拟对象.否则,它是一个存根.
假货确保测试顺利进行.这意味着未来测试的读者将理解虚假对象的行为,而无需阅读其源代码(无需依赖外部资源).
测试运行顺畅意味着什么?
例如下面的代码:
 public void Analyze(string filename)
        {
            if(filename.Length<8)
            {
                try
                {
                    errorService.LogError("long file entered named:" + filename);
                }
                catch (Exception e)
                {
                    mailService.SendEMail("admin@hotmail.com", "ErrorOnWebService", "someerror");
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)
你想测试mailService.SendEMail()方法,为了做到这一点你需要在你的测试方法中模拟一个Exception,所以你只需要创建一个Fake Stub errorService类来模拟那个结果,然后你的测试代码就可以测试了mailService.SendEMail()方法.如您所见,您需要模拟来自另一个外部依赖性ErrorService类的结果.
存根
\n存根是一个保存预定义数据并在测试期间使用它来应答调用的对象。当您可以\xe2\x80\x99t或不想\xe2\x80\x99t想要涉及将用真实数据回答或具有不良副作用的对象时,可以使用它。
\n一个示例可以是需要从数据库获取一些数据以响应方法调用的对象。我们引入了一个存根并定义了应该返回哪些数据,而不是真正的对象。
\n\n存根示例:
\npublic class GradesService {\n\n   private final Gradebook gradebook;\n\n   public GradesService(Gradebook gradebook) {\n       this.gradebook = gradebook;\n   }\n\n   Double averageGrades(Student student) {\n       return average(gradebook.gradesFor(student));\n   }\n}\nRun Code Online (Sandbox Code Playgroud)\n您无需从成绩册商店调用数据库来获取真实的学生成绩,而是预先配置存根并将返回成绩。您定义了足够的数据来测试平均计算算法。
\npublic class GradesServiceTest {\n\n   private Student student;\n   private Gradebook gradebook;\n\n   @Before\n   public void setUp() throws Exception {\n       gradebook = mock(Gradebook.class);\n       student = new Student();\n   }\n\n   @Test\n   public void calculates_grades_average_for_student() {\n       //stubbing gradebook\n       when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); \n\n       double averageGrades = new GradesService(gradebook).averageGrades(student);\n\n       assertThat(averageGrades).isEqualTo(8.0);\n   }\n}\nRun Code Online (Sandbox Code Playgroud)\n 嘲笑
\n模拟是注册它们收到的调用的对象。在测试断言中,您可以在模拟上验证是否执行了所有预期操作。当您不想调用生产代码或者没有简单的方法来验证预期代码是否已执行时,您可以使用模拟。没有返回值,也没有简单的方法来检查系统状态更改。一个示例可以是调用电子邮件发送服务的功能。
\n您不想\xe2\x80\x99 每次运行测试时都发送电子邮件。此外,在测试中验证是否发送了正确的电子邮件并不容易。您唯一能做的就是验证我们测试中所执行功能的输出。在其他世界中,验证是否调用了电子邮件发送服务。
\n\n模拟示例:
\npublic class SecurityCentral {\n\n   private final Window window;\n   private final Door door;\n\n   public SecurityCentral(Window window, Door door) {\n       this.window = window;\n       this.door = door;\n   }\n\n   void securityOn() {\n       window.close();\n       door.close();\n   }\n}\nRun Code Online (Sandbox Code Playgroud)\n您不想\xe2\x80\x99 想要关闭真正的门来测试安全方法是否有效,对吧?相反,您将门和窗模拟对象放置在测试代码中。
\npublic class SecurityCentralTest {\n\n   Window windowMock = mock(Window.class);\n   Door doorMock = mock(Door.class);\n\n   @Test\n   public void enabling_security_locks_windows_and_doors() {\n       SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);\n\n       securityCentral.securityOn();\n\n       verify(doorMock).close();\n       verify(windowMock).close();\n   }\n}\nRun Code Online (Sandbox Code Playgroud)\n非常感谢 Micha\xc5\x82 Lipski 的好文章。进一步阅读:
\n测试替身 \xe2\x80\x93 Martin Fowler https://martinfowler.com/bliki/TestDouble.html 
 \n测试替身 \xe2\x80\x93 xUnit 模式http://xunitpatterns.com/Test%20Double.html 
 \nMocks Aren \xe2\x80\x99t 存根 \xe2\x80\x93 Martin Fowler https://martinfowler.com/articles/mocksArentStubs.html 
 \n命令查询分离 \xe2\x80\x93 Martin Fowler https://martinfowler.com/bliki/ CommandQuerySeparation.html
正好来自纸模拟角色,而不是对象,由jMock的开发人员:
存根是生成代码的虚拟实现,可返回固定结果.模拟对象充当存根,但也包括用于检测目标对象与其邻居的交互的断言.
所以,主要的区别是:
总而言之,同时也试图驱散福勒的文章标题中的混乱:模拟是存根,但它们不仅仅是存根.
甲存根是用于假具有方法的对象的预编程的行为。您可能希望使用此方法而不是现有方法以避免不必要的副作用(例如,存根可能会进行虚假的 fetch 调用,该调用会返回预编程的响应,而实际上并未向服务器发出请求)。
甲模拟是用于假已对象的方法预编程行为以及预编程的期望。如果这些期望没有得到满足,那么模拟将导致测试失败(例如,模拟可以进行一个假的 fetch 调用,该调用返回一个预编程的响应,而不实际向服务器发出请求,该请求会期望例如第一个参数http://localhost:3008/不是测试将失败。)
与模拟不同,存根没有可能会导致测试失败的预编程期望。
那里有很多有效的答案,但我认为值得一提的是这种形式的鲍勃叔叔:https : //8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html
有史以来最好的例子解释!
他使用的通用术语是测试替身(想想特技替身)。测试替身是任何为了测试目的而替换生产对象的情况的通用术语。Gerard 列出了各种类型的 double:
模拟既是技术对象又是功能对象。
模拟是技术性的。由于字节码生成,它确实是由一个模拟库(EasyMock、JMockit 和最近的 Mockito 以这些而闻名)创建的。
模拟实现的生成方式使我们可以在调用方法时对其进行检测以返回特定值,但也可以进行其他一些操作,例如验证是否使用某些特定参数(严格检查)或任何参数调用了模拟方法(没有严格的检查)。    
实例化一个模拟:
@Mock Foo fooMock
Run Code Online (Sandbox Code Playgroud)
记录行为:
when(fooMock.hello()).thenReturn("hello you!");
Run Code Online (Sandbox Code Playgroud)
验证调用:
verify(fooMock).hello()
Run Code Online (Sandbox Code Playgroud)
这些显然不是实例化/覆盖 Foo 类/行为的自然方式。这就是我提到技术方面的原因。
但是模拟也是功能性的,因为它是我们需要与 SUT 隔离的类的实例。有了记录的行为,我们就可以像使用存根一样在 SUT 中使用它。
存根只是一个功能对象:它是我们需要与 SUT 隔离的类的实例,仅此而已。这意味着必须明确定义单元测试期间所需的存根类和所有行为装置。
例如,存根hello()需要对类进行子Foo类化(或实现它的接口)并覆盖hello()  :
public class HelloStub extends Hello{    
  public String hello { 
      return "hello you!"; 
  }
}
Run Code Online (Sandbox Code Playgroud)
如果另一个测试场景需要另一个值返回,我们可能需要定义一个通用的方法来设置返回值:
public class HelloStub extends Hello{    
  public HelloStub(String helloReturn){
       this.helloReturn = helloReturn;
  }
  public String hello { 
      return helloReturn; 
  }
}
Run Code Online (Sandbox Code Playgroud)
其他场景:如果我有一个副作用方法(无返回)并且我会检查该方法是否被调用,我可能应该在存根类中添加一个布尔值或一个计数器来计算调用该方法的次数。
结论
存根通常需要大量开销/代码来编写单元测试。由于提供开箱即用的录制/验证功能,模拟可以防止什么。
这就是为什么现在,随着优秀模拟库的出现,存根方法很少在实践中使用。  
关于 Martin Fowler 文章:当我使用模拟并且避免存根时,我不认为自己是一个“模拟者”程序员。
但是我在真正需要时使用模拟(烦人的依赖项),并且当我测试具有依赖项的类时,我更喜欢测试切片和小型集成测试,而模拟将是一种开销。   
加上有用的答案,使用 Mocks 比 Subs最强大的一点之一
如果合作者 [主要代码依赖于它]不在我们的控制之下(例如来自第三方库),
在这种情况下,stub 比 mock 更难编写。
|   归档时间:  |  
           
  |  
        
|   查看次数:  |  
           302223 次  |  
        
|   最近记录:  |