模拟和存根之间有什么区别?

问题描述 投票:799回答:34

我已经阅读了各种关于模拟和测试中存根的文章,包括Martin Fowler's Mocks Aren't Stubs,但仍然不明白其中的区别。

testing mocking stub
34个回答
648
投票

存根

我相信最大的区别是你已经用预定的行为编写了一个存根。所以你会有一个实现依赖的类(最有可能的抽象类或接口),你为了测试目的而伪装,这些方法只是用set响应来删除。他们不会做任何花哨的事情,你会在测试之外编写它的存根代码。

嘲笑

模拟是你的测试的一部分,你必须设置你的期望。模拟不是以预定方式设置的,因此您可以在测试中使用代码。某种方式的模拟是在运行时确定的,因为设置期望的代码必须在它们执行任何操作之前运行。

模拟和存根之间的区别

用模拟编写的测试通常遵循initialize -> set expectations -> exercise -> verify模式进行测试。虽然预先编写的存根将遵循initialize -> exercise -> verify

模拟与存根之间的相似性

两者的目的是消除测试类或函数的所有依赖关系,以便您的测试更加集中,更简单,他们试图证明。


19
投票

如果将它与调试进行比较:

Stub就像确保方法返回正确的值一样

模拟实际上就像进入方法并确保内部的所有内容都正确,然后返回正确的值。


18
投票

我认为他们之间最重要的区别是他们的意图。

让我试着在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
  • STUB:与twitter API的网络连接非常慢,这使我的测试变慢。我知道它会返回时间轴,所以我创建了一个模拟HTTP twitter API的存根,这样我的测试就可以非常快地运行,即使我离线也可以运行测试。
  • MOCK:我还没有编写任何我的UI方法,而且我不确定我需要为我的ui对象编写什么方法。我希望通过编写测试代码来了解我的控制器如何与我的ui对象协作。

通过编写mock,您可以通过验证期望得到满足来发现对象协作关系,而stub只模拟对象的行为。

如果你想了解更多关于模拟的信息,我建议阅读这篇文章:http://jmock.org/oopsla2004.pdf


16
投票

要非常清楚和实用:

Stub:一个实现要伪造的类/对象的方法的类或对象,它总是返回你想要的。

JavaScript中的示例:

var Stub = {
   method_a: function(param_a, param_b){
      return 'This is an static result';
   }
}

模拟:存根相同,但它添加了一些逻辑,在调用方法时“验证”,因此您可以确定某些实现正在调用该方法。

正如@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!');
   }
}

14
投票

这张幻灯片解释了主要的差异非常好。

enter image description here

*来自华盛顿大学的CSE 403第16讲(由“Marty Stepp”创作的幻灯片)


14
投票

使用心理模型确实帮助我理解了这一点,而不是所有的解释和文章,并没有完全“沉入”。

想象一下,你的孩子桌子上有一个玻璃盘,他开始玩它。现在,你担心它会破裂。所以,你给他一块塑料板。这将是一个模拟(相同的行为,相同的界面,“更软”的实现)。

现在,假设你没有塑料更换,所以你解释“如果你继续玩它,它会破坏!”。这是一个Stub,你事先提供了一个预定义的状态。

Dummy将是他甚至没有使用的叉子......而间谍可能就像提供你已经使用的相同解释一样有效。


11
投票

我喜欢Roy Osherove Qazxsw Poi提出的解释。

创建的每个类或对象都是假的。如果您验证对它的呼叫,它是一个模拟。否则它是一个存根。


10
投票
  • Stubs vs. Mocks 存根 提供方法调用的具体答案 例如:myStubbedService.getValues()只返回被测代码所需的字符串 被测试的代码用来隔离它 不能失败测试 例如:myStubbedService.getValues()只返回存根值 经常实现抽象方法 嘲弄 存根的“超集”;可以断言某些方法被调用 例如:验证myMockedService.getValues()仅被调用一次 用于测试被测代码的行为 可以通过测试失败 例:验证myMockedService.getValues()被调用一次;验证失败,因为我的测试代码没有调用myMockedService.getValues() 经常嘲笑接口

9
投票

伪是一个通用术语,可用于描述存根或模拟对象(手写或其他),因为它们看起来都像真实对象。

假的是存根还是模拟取决于它在当前测试中的使用方式。如果它用于检查交互(断言),则它是一个模拟对象。否则,它是一个存根。

假货确保测试顺利进行。这意味着未来测试的读者将理解虚假对象的行为,而无需阅读其源代码(无需依赖外部资源)。

测试运行顺畅意味着什么? 例如下面的代码:

 [video link]

你想测试mailService.SendEMail()方法,为了做到这一点你需要在你的测试方法中模拟一个Exception,所以你只需要创建一个Fake Stub errorService类来模拟那个结果,然后你的测试代码就可以测试了mailService.SendEMail()方法。如您所见,您需要模拟来自另一个外部依赖性ErrorService类的结果。


9
投票

让我看看测试双打:

  • 假:伪造是具有工作实现的对象,但与生产实现不同。如:数据访问对象或存储库的内存实现。
  • 存根:存根是一个保存预定义数据的对象,并在测试期间使用它来应答调用。如:一个对象需要从数据库中获取一些数据以响应方法调用。
  • 模拟:模拟是注册接收呼叫的对象。在测试断言中,我们可以在Mocks上验证所有预期的操作都已执行。如:调用电子邮件发送服务的功能。更多只需检查 public void Analyze(string filename) { if(filename.Length<8) { try { errorService.LogError("long file entered named:" + filename); } catch (Exception e) { mailService.SendEMail("[email protected]", "ErrorOnWebService", "someerror"); } } }

7
投票

从纸张this,由jMock的开发人员:

存根是生成代码的虚拟实现,可返回固定结果。模拟对象充当存根,但也包括用于检测目标对象与其邻居的交互的断言。

所以,主要的区别是:

  • 对存根设置的期望通常是通用的,而对模拟设置的期望可以更“聪明”(例如,在第一次调用时返回此信息,在第二次调用时返回此信息等)。
  • 存根主要用于设置SUT的间接输入,而模拟可用于测试SUT的间接输入和间接输出。

总而言之,同时也试图驱散Mock Roles, not Objects标题的混乱:模拟是存根,但它们不仅仅是存根。


762
投票

前言

对象有几种定义,不是真实的。一般术语是测试双倍。这个术语包括:虚拟,假,存根,模拟。

参考

Martin Fowler's article说:

  • 虚拟对象传递但从未实际使用过。通常它们仅用于填充参数列表。
  • 假对象实际上有工作实现,但通常需要一些使它们不适合生产的快捷方式(内存数据库就是一个很好的例子)。
  • 存根提供了在测试期间进行的调用的固定答案,通常不会对测试中编程的任何内容做出任何响应。存根还可以记录有关呼叫的信息,例如记住它'发送'的消息的电子邮件网关存根,或者可能只记录它'发送'的消息。
  • 模拟是我们在这里所讨论的:预编程的对象具有预期,形成了预期接收的调用的规范。

样式

Mocks vs Stubs =行为测试与状态测试

原理

根据每次测试只测试一件事的原则,在一次测试中可能有几个存根,但通常只有一个模拟。

生命周期

使用存根测试生命周期:

  1. 设置 - 准备正在测试的对象及其存根协作者。
  2. 练习 - 测试功能。
  3. 验证状态 - 使用断言检查对象的状态。
  4. 拆解 - 清理资源。

使用模拟测试生命周期:

  1. 设置数据 - 准备正在测试的对象。
  2. 设置期望 - 准备主要对象正在使用的模拟期望。
  3. 练习 - 测试功能。
  4. 验证期望 - 验证是否已在mock中调用了正确的方法。
  5. 验证状态 - 使用断言检查对象的状态。
  6. 拆解 - 清理资源。

摘要

模拟和存根测试都给出了问题的答案:结果是什么?

使用模拟测试也对以下方面感兴趣:结果如何实现?


4
投票

请参阅下面使用C#和Moq框架的mocks vs stubs示例。 Moq没有Stub的特殊关键字,但您也可以使用Mock对象创建存根。

Fowler's article

4
投票

我从UncleBob namespace UnitTestProject2 { using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; [TestClass] public class UnitTest1 { /// <summary> /// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero /// </summary> [TestMethod] public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce() { // Arrange var mockEntityRepository = new Mock<IEntityRepository>(); mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>())); var entity = new EntityClass(mockEntityRepository.Object); // Act var name = entity.GetNameWithPrefix(12); // Assert mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once); } /// <summary> /// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero /// </summary> [TestMethod] public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled() { // Arrange var mockEntityRepository = new Mock<IEntityRepository>(); mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>())); var entity = new EntityClass(mockEntityRepository.Object); // Act var name = entity.GetNameWithPrefix(0); // Assert mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never); } /// <summary> /// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix /// </summary> [TestMethod] public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix() { // Arrange var stubEntityRepository = new Mock<IEntityRepository>(); stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>())) .Returns("Stub"); const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub"; var entity = new EntityClass(stubEntityRepository.Object); // Act var name = entity.GetNameWithPrefix(12); // Assert Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name); } } public class EntityClass { private IEntityRepository _entityRepository; public EntityClass(IEntityRepository entityRepository) { this._entityRepository = entityRepository; } public string Name { get; set; } public string GetNameWithPrefix(int id) { string name = string.Empty; if (id > 0) { name = this._entityRepository.GetName(id); } return "Mr. " + name; } } public interface IEntityRepository { string GetName(int id); } public class EntityRepository:IEntityRepository { public string GetName(int id) { // Code to connect to DB and get name based on Id return "NameFromDb"; } } } 那里看到了这篇有趣的文章。它以一种非常容易理解的方式解释了所有术语,因此对初学者很有用。 Martin Fowlers的文章特别适合像我这样的初学者。


4
投票

Stub和Mock测试观点:

  • Stub是用户以静态方式完成的虚拟实现,即在Stub中编写实现代码。所以它无法处理服务定义和动态条件,通常这是在JUnit框架中完成的,不使用模拟框架。
  • Mock也是虚拟实现,但它的实现通过使用像Mockito这样的Mocking框架以动态方式完成。因此,我们可以将条件和服务定义作为动态方式处理,即可以在运行时从代码动态创建模拟。所以使用mock我们可以动态地实现Stubs。

3
投票

Stub帮助我们进行测试。怎么样?它给出了有助于运行测试的值。这些值本身并不真实,我们创建这些值只是为了运行测试。例如,我们创建一个HashMap,为我们提供与数据库表中的值类似的值。因此,我们不是直接与数据库交互,而是与Hashmap进行交互。

模拟是一个运行测试的假对象。我们把断言放在哪里。


3
投票

Mockito的例子

The Little Mocker

Stub只返回固定数据。存根很简单直接 - 它们本质上是方法的最简单实现,并且每次都返回相同的固定数据。这使我们可以完全控制从依赖项上调用的方法返回的值。

Mock对象提供了一种检查被测对象是否已调用某些方法的方法。

正如Martin Fowler在他的enter image description here中所说的那样

有一点不同,essay使用状态验证,而stub使用行为验证。

阅读更多mockhere


2
投票

我在答案中使用了python示例来说明差异。

Stub - Stubbing是一种软件开发技术,用于在开发生命周期的早期实现类的方法。它们通常用作占位符,用于实现已知接口,其中接口已完成或已知,但实现尚未知晓或最终确定。您从存根开始,这只是意味着您只需要编写函数的定义并保留实际代码以供日后使用。优点是你不会忘记方法,你可以在代码中看到它时继续考虑你的设计。您还可以让存根返回静态响应,以便响应可以立即被代码的其他部分使用。存根对象提供有效的响应,但无论您传入什么输入,它都是静态的,您将始终获得相同的响应:

here

模拟对象用于模拟测试用例,它们验证在这些对象上调用某些方法。模拟对象是模拟对象,以受控方式模拟真实对象的行为。您通常会创建一个模拟对象来测试其他对象的行为。模拟让我们模拟单元测试不可用或太笨重的资源。

没有module.朋友:

class Foo(object):
    def bar1(self):
        pass

    def bar2(self):
        #or ...
        raise NotImplementedError

    def bar3(self):
        #or return dummy data
        return "Dummy Data"

test.朋友:

import os
import os.path

def rm(filename):
    if os.path.isfile(filename):
        os.remove(filename)

这是一个非常基本的例子,只运行rm并断言它被调用的参数。您可以将mock与对象一起使用,而不仅仅是此处所示的函数,您还可以返回一个值,以便可以使用模拟对象替换存根进行测试。

更多关于from mymodule import rm import mock import unittest class RmTestCase(unittest.TestCase): @mock.patch('mymodule.os') def test_rm(self, mock_os): rm("any path") # test that rm called os.remove with the right parameters mock_os.remove.assert_called_with("any path") if __name__ == '__main__': unittest.main() ,请注意python 2.x中的mock不包含在unittest中,但它是一个可下载的模块,可以通过pip(pip install mock)下载。

我还读过Roy Osherove的“单元测试艺术”,我认为如果使用Python和Python的例子编写类似的书,那将会很棒。如果有人知道这样的书,请分享。干杯:)


2
投票

存根是为测试目的而构建的虚假对象。模拟是一个存根,它记录预期的调用是否有效发生。


2
投票

存根是一个空函数,用于在测试期间避免未处理的异常:

unittest.mock

模拟是一种人工函数,用于在测试期间避免操作系统,环境或硬件依赖:

function foo(){}

在断言和状态方面:

  • 在事件或状态改变之前,模拟被断言
  • 存根未被断言,它们在事件之前提供状态以避免从不相关的单元执行代码
  • 间谍设置为存根,然后在事件或状态更改后断言
  • 伪造没有被断言,它们在具有硬编码依赖性的事件之后运行以避免状态

参考


2
投票

那里有很多有效的答案,但我觉得值得一提的是这个形式的叔叔鲍勃:Test Doubles: Fakes, Mocks and Stubs

有例子的最佳解释!


1
投票

Stub是一个实现组件接口的对象,但不是返回组件在调用时返回的内容,而是可以将存根配置为返回适合测试的值。使用存根,单元测试可以测试单元是否可以处理来自其协作者的各种返回值。在单元测试中使用存根而不是真正的协作者可以表达如下:

单元测试 - >存根

单元测试 - >单元 - >存根

单元测试断言结果和单元状态

首先,单元测试创​​建存根并配置其返回值。然后单元测试创​​建单元并在其上设置存根。现在单元测试调用单元,而单元又调用存根。最后,单元测试会对单元上方法调用的结果进行断言。

模拟就像一个存根,只有它还有一些方法可以确定模拟调用的方法。因此,使用模拟可以测试单元是否可以正确处理各种返回值,以及单元是否正确使用协作者。例如,您无法通过dao对象返回的值查看是否使用Statement或PreparedStatement从数据库中读取数据。你也不能看到在返回值之前是否调用了connection.close()方法。这可以通过模拟实现。换句话说,模拟可以测试单元与协作者的完整交互。不只是返回单位使用的值的协作者方法。在单元测试中使用模拟可以表达如下:

单元测试 - >模拟

单元测试 - >单元 - >模拟

单元测试断言单元的结果和状态

单元测试断言在mock上调用的方法

更多细节>> https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html


294
投票

存根是一个简单的假对象。它只是确保测试顺利进行。 模拟是一个更聪明的存根。您验证测试是否通过它。


0
投票

以下是我的理解......

  • 如果您在本地创建测试对象并使用它来提供本地服务,则使用模拟对象。这将测试您在本地服务中实现的方法。它用于验证行为
  • 当你从真实服务提供者那里得到测试数据时,虽然从测试版本的接口获得对象的测试版本,你正在使用存根,存根可以有逻辑来接受某些输入并给出相应的输出来帮助你执行国家核查......

213
投票

以下是对每个示例的描述,然后是真实世界的示例。

  • 虚拟 - 只是虚假的价值,以满足API。 示例:如果您正在测试一个类的方法,该类在构造函数中需要许多强制参数而对测试没有影响,那么您可以创建虚拟对象以创建类的新实例。
  • 假 - 创建一个可能依赖于某些外部基础结构的类的测试实现。 (这是一种很好的做法,你的单元测试实际上并不与外部基础设施交互。) 示例:创建用于访问数据库的虚假实现,将其替换为in-memory集合。
  • 存根 - 覆盖返回硬编码值的方法,也称为state-based。 示例:您的测试类依赖于Calculate()方法需要5分钟才能完成。您可以使用返回硬编码值的存根替换其实际实现,而不是等待5分钟;只占一小部分时间。
  • 模拟 - 非常类似于Stub但是interaction-based而不是基于状态。这意味着你不希望Mock返回一些值,而是假设方法调用的特定顺序。 示例:您正在测试用户注册类。在调用Save之后,它应该调用SendConfirmationEmail

StubsMocks实际上是Mock的子类型,它们都与测试实现交换实际实现,但出于不同的具体原因。


162
投票

codeschool.com课程中,Rails Testing for Zombies,他们给出了这些术语的定义:

存根

用于使用返回指定结果的代码替换方法。

嘲笑

一个断言,断言该方法被调用。

正如Sean Copenhaver在他的回答中所描述的那样,区别在于模拟设定了期望(即做出断言,关于是否或如何被调用)。


124
投票

存根不会使您的测试失败,模拟可以。


30
投票

我认为关于这个问题的最简单和更清晰的答案来自Roy Osherove在他的书The art of Unit Testing(第85页)

告诉我们处理存根的最简单方法是注意存根永远不会使测试失败。断言测试用途总是针对被测试的类。

另一方面,测试将使用模拟对象来验证测试是否失败。 [...]

同样,模拟对象是我们用来查看测试是否失败的对象。

这意味着如果你正在对假冒进行断言,这意味着你使用假冒作为模拟,如果你只使用假冒运行测试而没有断言,你使用假冒作为存根。


25
投票

阅读上面的所有解释,让我试着浓缩:

  • 存根:一段允许测试运行的虚拟代码,但您不关心它发生了什么。
  • 模拟:一段虚拟代码,作为测试的一部分,您可以正确调用VERIFY。
  • 间谍:一段虚拟代码,拦截对真实代码的一些调用,允许您在不替换整个原始对象的情况下验证调用。

21
投票

模拟只是测试行为,确保调用某些方法。 Stub是特定对象的可测试版本(本身)。

你是什​​么意思Apple方式?

© www.soinside.com 2019 - 2024. All rights reserved.