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

时间:2010-08-11 14:19:57

标签: testing mocking stub

我已经阅读了有关模拟与测试中存根的各种文章,包括Martin Fowler's Mocks Aren't Stubs,但仍然不理解其中的差异。

41 个答案:

答案 0 :(得分:743)

前言

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

参考

根据Martin Fowler's article

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

风格

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

原理

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

生命周期

使用存根测试生命周期:

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

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

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

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

答案 1 :(得分:632)

<强>存根

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

<强>模拟

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

模拟与存根之间的区别

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

模拟与存根之间的相似性

两者的目的是消除测试类或函数的所有依赖关系,这样您的测试就会更加集中,更简单。

答案 2 :(得分:278)

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

答案 3 :(得分:205)

以下是每个人的描述,然后是现实世界的样本。

  • Dummy - 只是虚假的值来满足API

      

    示例:如果您正在测试一个类的方法,该类需要在测试中无效的构造函数中需要许多必需参数,那么您可以创建虚拟用于创建类的新实例的对象。

  • - 创建可能依赖某些外部基础架构的类的测试实现。 (您的单元测试 NOT 实际上与外部基础架构交互是一种很好的做法。)

      

    示例:创建用于访问数据库的虚假实现,将其替换为in-memory集合。

  • 存根 - 覆盖返回硬编码值的方法,也称为state-based

      

    示例:您的测试类依赖于方法Calculate()需要5分钟才能完成。您可以使用返回硬编码值的存根替换其实际实现,而不是等待5分钟;只花了一小部分时间。

  • 模拟 - 与Stub非常相似,但与interaction-based非常相似,而不是基于状态。这意味着你不希望Mock返回一些值,而是假设方法调用的特定顺序。

      

    示例:您正在测试用户注册类。致电Save后,应致电SendConfirmationEmail

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

答案 4 :(得分:161)

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

存根

  

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

模拟

  

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

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

答案 5 :(得分:121)

Stubs不会让你的测试失败,模拟可以。

答案 6 :(得分:29)

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

  

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

     

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

     

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

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

答案 7 :(得分:25)

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

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

答案 8 :(得分:20)

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

你是什么意思Apple方式?

答案 9 :(得分:19)

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

  

存根就像确保方法返回正确的值

一样      

模拟就像踩到方法并确保内部的所有内容都正确无误后再返回正确的值。

答案 10 :(得分:18)

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

让我试着用为什么存根为什么模拟

来解释它

假设我正在为我的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

答案 11 :(得分:16)

非常明确和实用:

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

JavaScript中的示例:

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

模拟:存根相同,但它增加了一些“#34;验证&#34;当一个方法被调用时,你可以确定一些实现正在调用该方法。

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

答案 12 :(得分:14)

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

enter image description here

*来自华盛顿大学的CSE 403第16讲(幻灯片由&#34; Marty Stepp创建&#34;)

答案 13 :(得分:13)

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

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

现在,说您没有塑料替代品,因此您解释说“如果继续使用它,它将损坏!”。这是 Stub ,您已预先提供了预定义状态。

虚拟将是他甚至不使用的叉子...而间谍可能类似于提供与您已经使用过的解释相同的东西。

答案 14 :(得分:11)

我喜欢Roy Osherove [video link]提出的解释。

  

创建的每个类或对象都是假的。如果你验证,这是一个模拟   打电话给它。否则它是一个存根。

答案 15 :(得分:10)

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

答案 16 :(得分:9)

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

假的是存根还是模拟取决于它是如何使用的 目前的测试。如果它用于检查交互(断言),那就是a 模拟对象。否则,它是一个存根。

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

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

 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");
                }
            }
        }

您想测试 mailService.SendEMail()方法,为此您需要在测试方法中模拟Exception,因此您只需要创建一个Fake Stub errorService类来模拟该结果,那么你的测试代码将能够测试mailService.SendEMail()方法。如您所见,您需要模拟来自另一个外部依赖性ErrorService类的结果。

答案 17 :(得分:9)

请参阅测试双打:

  • 伪造:伪造是具有有效实现的对象,但与生产实现不同。 例如:数据访问对象或存储库的内存实现。
  • 存根:存根是一个对象,用于保存预定义的数据,并在测试过程中使用它来应答呼叫。 例如:需要从数据库中获取一些数据以响应方法调用的对象。

  • 模拟:模拟是注册收到的呼叫的对象。 在测试断言中,我们可以在Mocks上验证是否已执行所有预期的操作。 例如:一种调用电子邮件发送服务的功能。 有关更多信息,请检查this

答案 18 :(得分:7)

来自论文Mock Roles, not Objects,来自jMock的开发者:

  

Stubs是返回罐头的生产代码的虚拟实现   结果。模拟对象充当存根,但也包括断言   检测目标对象与其邻居的交互。

因此,主要区别在于:

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

总结一下,同时也试图驱散Fowler's article标题的混淆:模拟是存根,但它们不仅仅是存根

答案 19 :(得分:5)

存根

存根是用于伪造具有预编程行为的方法的对象。您可能要使用此方法而不是现有方法,以避免不必要的副作用(例如,存根可能会进行伪造的fetch调用,该调用会返回预先编程的响应,而无需实际向服务器发出请求。

模拟

模拟是用于伪造具有预编程行为以及预编程期望的方法的对象。如果未满足这些期望,则该模拟将导致测试失败(例如,模拟可能会进行伪造的fetch调用,该调用将返回预先编程的响应,而不会实际向服务器发出期望的请求例如第一个参数为http://localhost:3008/,否则测试将失败。)

差异

与模拟不同,存根不具有可能无法通过测试的预编程期望。

答案 20 :(得分:5)

加上有用的答案,使用Mocks而非Subs最强大的功能之一

如果协作者(主要代码所依赖的协作者)不受我们的控制(例如,来自第三方库),
在这种情况下,存根比模拟更难写

答案 21 :(得分:4)

我在UncleBob The Little Mocker看到了这篇有趣的文章。它以一种非常容易理解的方式解释了所有术语,因此对初学者很有用。 Martin Fowlers的文章特别适合像我这样的初学者。

答案 22 :(得分:4)

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

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";
        }
    }
}

答案 23 :(得分:4)

Stub和Mock测试观点:

  • Stub 是用户以静态方式完成的虚拟实现,即在Stub中编写实现代码。所以它无法处理服务定义和动态条件,通常这是在JUnit框架中完成的,不使用模拟框架。

  • 模拟也是虚拟实现,但是通过使用像Mockito这样的模拟框架,它的实现方式是动态。因此,我们可以将条件和服务定义作为动态方式处理,即可以在运行时从代码动态创建模拟。 因此,使用mock我们可以动态实现Stub。

答案 24 :(得分:3)

我正在阅读The Art of Unit Testing,偶然发现了以下定义:

  

是一个通用术语,可用于描述存根或模拟对象(手写或其他形式),因为它们看起来都像真实对象。假货是存根还是假货,取决于当前测试中的使用方式。如果用于检查互动(认定为无效),则为 模拟对象 。否则,它是 stub

答案 25 :(得分:2)

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

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

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

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

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

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

mymodule.py:

import os
import os.path

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

test.py:

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()

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

更多关于unittest.mock,请注意python 2.x中的mock不包含在unittest中,但它是一个可下载的模块,可以通过pip(pip install mock)下载。

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

答案 26 :(得分:2)

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

答案 27 :(得分:2)

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

function foo(){}

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

function foo(bar){ window = this; return window.toString(bar); }

在断言和陈述方面:

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

<强>参考

答案 28 :(得分:2)

那里有很多有效的答案,但我觉得值得一提的是这个形式的叔叔鲍勃: https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html

有关例子的最佳解释!

答案 29 :(得分:2)

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

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

答案 30 :(得分:2)

存根是一个测试double,它向SUT返回值。

模拟是测试双重测试,测试用于验证SUT正确调用依赖项。

此外,模拟通常是存根

答案 31 :(得分:2)

Mockito示例

enter image description here

存根仅返回固定数据。存根非常简单明了-实际上,它们是方法的最简单实现,并且每次都返回相同的罐头数据。这使我们可以完全控制从依赖项调用的方法返回的值。

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

正如马丁·福勒(Martin Fowler)在他的essay中所说

  

有一个区别,stub使用状态验证,而mock使用行为验证。

了解更多herehere

答案 32 :(得分:1)

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

单元测试 - &gt;存根

单元测试 - &gt;单位 - &gt;存根

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

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

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

单元测试 - &gt;模拟

单元测试 - &gt;单位 - &gt;模拟

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

单元测试断言模拟

上调用的方法

更多详情&gt;&gt; Here

答案 33 :(得分:0)

Mocks:帮助模拟和检查结果交互。这些互动 是 SUT 对其依赖项进行的调用以更改其状态。

存根:帮助模拟传入的交互。这些相互作用被称为 SUT 依赖其获取输入数据。

enter image description here

来源:单元测试原则、实践和模式 - Manning

答案 34 :(得分:0)

模拟既是技术对象又是功能性对象。

模拟是技术性的。它确实是由模拟库创建的(EasyMock,JMockit和最近的Mockito都为此而闻名),这要归功于字节码生成
它的实现是生成的,这种方式使得我们可以 instrument ,使其在调用方法时返回特定值,还可以进行其他一些操作,例如验证是否调用了模拟方法带有某些特定参数(严格检查)或其他任何参数(不严格检查)。

实例化一个模拟

@Mock Foo fooMock

记录行为:

when(fooMock.hello()).thenReturn("hello you!");

验证调用:

verify(fooMock).hello()

但是模拟也是功能性的,因为它是我们需要与SUT隔离的类的实例。有了记录的行为,我们可以像使用存根一样在SUT中使用它。


存根只是一个功能对象:这是我们需要与SUT隔离的类的实例,仅此而已。 这意味着必须明确定义单元测试过程中所需的存根类和所有行为夹具。
例如,对hello()进行存根将需要继承Foo类(或实现其具有的接口)并覆盖hello()

public class HelloStub extends Hello{    
  public String hello { 
      return "hello you!"; 
  }
}

如果另一个测试场景需要另一个值返回,我们可能需要定义一种通用的方式来设置返回值:

public class HelloStub extends Hello{    
  public HelloStub(String helloReturn){
       this.helloReturn = helloReturn;
  }
  public String hello { 
      return helloReturn; 
  }
}

其他情况:如果我有一个副作用方法(不返回),并且要检查是否调用了某个方法,则可能应该在存根类中添加一个布尔值或计数器来计算该方法被调用的次数。


结论

存根通常需要很多开销/代码来编写单元测试。开箱即用的提供记录/验证功能,可以防止什么模拟操作。
这就是为什么如今,随着优秀的模拟库的出现,在实践中很少使用存根方法。


关于Martin Fowler的文章:当我使用Mockito并避免存根时,我不认为自己是“模拟主义者”程序员。
但是我在真正需要时使用了模拟(使依赖项烦恼),并且当我测试具有依赖项的类时,我更喜欢测试切片/小型集成测试,而模拟将是一项开销。

答案 35 :(得分:0)

Stub和Mocks都覆盖了外部依赖关系,但区别在于

存根-> 要测试数据

模拟-> 测试行为


伪造/伪造-> 不进行任何测试(只需使用空方法覆盖功能,例如,替换Logger即可避免测试时出现记录噪音)

答案 36 :(得分:0)

假设您要测试一个名为EmployeeService的类,并且该类对名为EmployeeDao的接口具有依赖性:

public class EmployeeService{
   private EmployeeDao dao;
   public EmployeeService(Dao dao){this.dao = dao;}

   public String getEmployeeName(int id){
     Employee emp = bar.goToDatabaseAndBringTheEmployeeWithId(id);
     return emp != null?emp.getFullName:null;
   }
   //Further state and behavior
}

public interface EmployeeDao{
  Employee goToDatabaseAndBringTheEmployeeWithId(int id);
}

在您的测试课中:

public class EmployeeServiceTest{
   EmployeeService service;
   EmployeeDao mockDao = Mockito.mock(EmployeeDao.class);//Line 3

   @Before
   public void setUp(){
     service = new EmployeeService(mockDao);
   }
   //Tests
   //....
}

在上面第3行的测试类中,我们对模拟框架(在本例中为Mockito)说:“嘿,Mockito,请为我制作一个具有EmployeeDao功能的对象。”该框架将创建一个对象,该对象具有方法goToDatabaseAndBringTheEmployeeWithId但实际上没有主体。指示模拟对象做什么是您的工作。这是一个模拟。

但是您也可以创建一个实现EmployeeDao接口的类,并在测试类中使用它:

public EmployeeDaoStub implements EmployeeDao{
   public Employee goToDatabaseAndBringTheEmployeeWithId(int id){
      //No trip to DB, just returning a dummy Employee object
      return new Employee("John","Woo","123 Lincoln str");
   }
}

这次使用存根而不是模拟在您的测试类中:

public class EmployeeServiceTest{
   EmployeeService service;
   EmployeeDao daoStub = new EmployeeDaoStub();//Line 3

   @Before
   public void setUp(){
     service = new EmployeeService(daoStub);
   }
   //Tests
   //....
}

因此,将它们全部包装起来,存根是您创建(或其他人创建的)的类,专门用于模仿某些依赖关系,只是为了具有所需的状态。是的,正如所有其他人所说,它主要是关于一种状态的,而模拟通常是由模拟框架创建的,而您不知道其外观如何。但是,通过存根,您知道将要获得的班级:这是您创建的班级。

哦,顺便说一句,如果您的依赖项是类而不是接口,则可以扩展该类以创建存根。

答案 37 :(得分:0)

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

  • 如果您在本地创建测试对象并使用该服务提供本地服务,则使用模拟对象。 这将测试您在本地服务中实现的方法。 它用于验证行为

  • 当您从真实服务提供商那里获得测试数据时,虽然从测试版本的接口获得了对象的测试版本,但您正在使用存根 存根可以有逻辑来接受某些输入并给出相应的输出来帮助你执行状态验证......

答案 38 :(得分:0)

测试对象根据某些提示(功能调用)或其他刺激执行动作。这是测试情况的​​具体示例。

方案-EMT学生考试

一个学生已经学习成为紧急医疗技术员。如果您不熟悉这种测试情况,请在 Shameless 第6季第10集中观看Ian Gallagher。

为测试目的而找到患有各种疾病的患者太昂贵了。相反,我们使用演员。我们问测试对象(Ian):“您到达现场,患者处于固定状态并且失去知觉,您首先要做什么?”伊恩回答“我检查现场是否安全”。测试老师说“现场很安全”。

讲师(和演员)能够为测试对象的查询注入任意答案。

在这里,讲师(和演员)都是模拟对象。医学培训使用的术语(例如模拟代码模拟)与计算机科学家相同。

场景-注册网站

您正在测试Yahoo,这是您听说过的新电子邮件服务。要注册,您必须提供生日和其他干扰性问题的答案。

该网站要求您年满21岁。因此,您输入的值是1970年1月1日。它满足要求,并且使您免于实施“记住我的生日并键入内容”工作流的繁琐过程。

此日期为存根。该词的用法特定于计算机科学。

答案 39 :(得分:0)

Stubs用于具有您在测试中设置的预期返回值的方法。 模拟用于void方法,在Assert中验证它们被称为。

答案 40 :(得分:0)

模拟 - 模拟拦截对方法或函数(或一组方法和函数,如模拟类的情况)的调用。它不是该方法或功能的替代品。在该拦截中,模拟可以做任何想做的事情,例如记录输入和输出,决定短路呼叫,改变返回值等。

存根 - 存根是一个方法或函数(或一组方法和函数,如在存根类的情况下)的有效完整工作实现,它具有相同的接口/签名。它的方法,功能或方法和功能组。存根实现通常只会在单元测试的上下文中执行可接受的事情,这意味着它不会做IO,例如,模仿它所存在的事物的行为。