【问题标题】:What's the difference between a mock & stub?模拟和存根有什么区别?
【发布时间】:2011-03-28 10:06:12
【问题描述】:

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

【问题讨论】:

标签: testing mocking stub


【解决方案1】:

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

Apple 方式是什么意思?

【讨论】:

  • “Apple 方式是什么意思?”使用 Helvetica
  • 苹果的方式而不是微软的方式:)
【解决方案2】:

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

【讨论】:

  • 我认为这是最简洁明了的答案。要点:模拟 IS-A 存根。 stackoverflow.com/a/17810004/2288628 是这个答案的较长版本。
  • 我不认为模拟是存根。模拟用于断言并且不应该返回数据,存根用于返回数据并且不应该断言。
  • @dave1010 Mocks 绝对可以返回数据甚至抛出异常。他们应该这样做以响应传递给他们的参数。
  • @trenton 如果一个对象根据传入的数据返回或抛出,那么它是 fake,而不是模拟。存根测试您的 SUT 如何处理接收消息,模拟测试您的 SUT 如何发送消息。将两者混为一谈可能会导致糟糕的 OO 设计。
  • 我认为这很棒 - 存根返回问题的答案。模拟也返回问题的答案(is-a stub),但它也验证问题被问到了!!
【解决方案3】:

以下是我的理解……

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

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

【讨论】:

    【解决方案4】:

    存根

    我相信最大的区别是您已经编写了具有预定行为的存根。因此,您将拥有一个实现您为测试目的而伪造的依赖项(最有可能是抽象类或接口)的类,并且这些方法将被设置响应。他们不会做任何花哨的事情,而且您已经在测试之外为其编写了存根代码。

    模拟

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

    模拟和存根之间的区别

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

    模拟和存根之间的相似性

    两者的目的都是为了消除对类或函数的所有依赖项的测试,以便您的测试更加集中和更简单地证明它们。

    【讨论】:

    • 一条令人困惑的行 - A mock is something that as part of your test you have to setup with your expectations. A mock is not setup in a predetermined way so you have code that does it in your test. 那么,它是根据您的期望设置的,但不是以预先确定的方式?这怎么可能?
    【解决方案5】:

    存根不会通过测试,模拟可以。

    【讨论】:

    • 我认为这很好,你知道重构后测试是否有相同的行为。
    • @RodriKing 我也有同样的感觉。与 Mock 一样,生产代码的任何更改 - 您都会对测试代码进行相应的更改。哪个是痛!使用 Stubs,感觉就像您一直在测试行为,因此无需对测试代码进行细微更改。
    【解决方案6】:

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

    存根

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

    模拟

    带有方法被调用的断言的存根。

    因此,正如 Sean Copenhaver 在他的回答中所描述的,不同之处在于 mock 设定了期望(即做出断言,关于它们是否或如何被调用)。

    【讨论】:

    • 为了补充 Dillon 的帖子,想想这个,你有一个名为“MakeACake”的类,它使用了几个库:牛奶、鸡蛋、糖、烤箱。
    【解决方案7】:

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

    让我尝试在 WHY stubWHY 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

    【讨论】:

    • 我认为你的想法是对的,但 Dillon Kearns 解释得更清楚了。
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      前言

      对象有几种定义,它们不是真实的。总称是test double。该术语包括:dummyfakestubmock

      参考

      根据Martin Fowler's article

      • Dummy 对象被传递但从未实际使用过。通常它们只是用来填充参数列表。
      • Fake 对象实际上有工作实现,但通常采取一些捷径,这使得它们不适合生产(内存数据库就是一个很好的例子)。
      • 存根为测试期间拨打的电话提供预设答案,通常根本不响应任何超出测试程序的内容。存根还可以记录有关呼叫的信息,例如记住它“发送”的消息的电子邮件网关存根,或者可能只记录它“发送”的消息的数量。
      • Mocks 就是我们在此讨论的内容:预编程的对象具有期望,这些期望形成了它们期望接收的调用的规范。

      风格

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

      原则

      根据每次测试只测试一件事的原则,一个测试可能有多个stub,但一般只有一个mock。

      生命周期

      使用存根测试生命周期:

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

      使用模拟测试生命周期:

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

      总结

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

      使用 mock 进行测试也感兴趣:结果如何?

      【讨论】:

      • 等等,模拟也返回预设答案?否则他们为什么要回答这个问题?
      • 从你写的我可以看出,模拟 = 存根 + 期望和验证,因为模拟“为测试期间的调用提供预设答案,通常根本不响应任何超出为测试”(与存根相同)。 Fowler 作为存根示例展示的示例实际上是间谍的示例!这意味着模拟是存根,间谍是存根。存根只是一个具有多种工作方法的对象。这也解释了为什么 Mockito 不推荐使用 stub() 方法。
      • 我对此感到困惑,接受的答案是这个“期望设置”,它甚至意味着什么?通常,在“主代码”中,您会创建您期望的结果。听起来您以某种方式将期望放入模拟对象中,但这对我来说没有意义。此外,您可以通过一些输入轻松地练习模拟,存储结果,稍后创建“期望”,然后进行比较。你使用的术语我觉得太抽象和模棱两可。
      • 为什么这个答案会出现在第二页?它应该出现在接受的答案之后(如果不是在它之前)。我在这里看到的最佳答案,详细、准确且易于理解。
      【解决方案10】:

      如果你把它比作调试:

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

      Mock实际上就像是进入方法,并在返回正确的值之前确保里面的一切都是正确的。

      【讨论】:

        【解决方案11】:

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

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

        【讨论】:

        • “所以我们不是直接与数据库交互,而是与 Hashmap 交互。” ...因为当时还没有时间编写数据库模块,而且如果不使用存根,我们就无法运行测试代码。否则,同样的 Hasmap 将是一个模拟!对吗?
        【解决方案12】:

        请参阅下面使用 C# 和 Moq 框架的模拟与存根示例。 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";
                }
            }
        }
        

        【讨论】:

          【解决方案13】:

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

          fake 是 stub 还是 mock 取决于它在 当前的测试。如果它用于检查交互(断言反对),它是 模拟对象。否则,它就是一个存根。

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

          测试运行顺利是什么意思?
          例如下面的代码:

           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() 方法,为此您需要在测试方法中模拟一个异常,因此您只需要创建一个 Fake Stub errorService 类来模拟该结果,那么您的测试代码将能够测试 mailService.SendEMail() 方法。如您所见,您需要模拟来自另一个 External Dependency ErrorService 类的结果。

          【讨论】:

            【解决方案14】:

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

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

            【讨论】:

            • 好答案。根据您的定义,Mock 听起来与 Spy 非常相似。如果您更新答案以包含更多测试替身,那就太好了。
            【解决方案15】:

            这里是对每一个的描述,然后是真实世界的样本。

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

              示例:如果您正在测试一个类的方法,该方法在构造函数中需要许多强制参数,而 对您的测试没有影响,那么您可以创建 dummy用于创建类的新实例的对象。

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

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

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

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

            • Mock - 与Stub 非常相似,但interaction-based 不是基于状态的。这意味着您不希望 Mock 返回某个值,而是假设进行了特定的方法调用顺序。

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

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

            【讨论】:

            • 1) 阅读答案 2) 阅读blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html 3) 再次阅读答案。
            • 我很少在 SO 上留下 cmets,但这个答案显然值得竖起大拇指。清晰、简洁并带有示例。另外,感谢分享帖子@snr
            • “Stubs 和 Mocks 实际上是 Mock 的子类型” --- 你的意思是 Stubs 和 Fakes?
            【解决方案16】:

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

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

            class Foo(object):
                def bar1(self):
                    pass
            
                def bar2(self):
                    #or ...
                    raise NotImplementedError
            
                def bar3(self):
                    #or return dummy data
                    return "Dummy Data"
            

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

            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 与对象一起使用,而不仅仅是此处所示的函数,还可以返回一个值,以便可以使用 mock 对象替换存根进行测试。

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

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

            【讨论】:

              【解决方案17】:

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

              单元测试 --> 存根

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

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

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

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

              单元测试 --> 模拟

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

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

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

              更多详情>>Here

              【讨论】:

                【解决方案18】:

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

                function foo(){}
                

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

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

                就断言和状态而言:

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

                参考文献

                【讨论】:

                • +1 用于将间谍添加到词汇表中。另外,我认为您的意思是“间谍像模拟一样设置”而不是“间谍像存根一样设置”
                【解决方案19】:

                要非常清楚和实用:

                存根:实现要伪造的类/对象的方法的类或对象,并始终返回您想要的。

                JavaScript 中的示例:

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

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

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

                【讨论】:

                  【解决方案20】:

                  我认为Roy Osherove在他的书单元测试的艺术(第85页)中给出了关于这个问题的最简单和更清晰的答案

                  判断我们正在处理存根的最简单方法是注意存根永远不会通过测试。测试使用的断言总是反对 被测试的类。

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

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

                  Stub 和 mock 都是假的。

                  如果你对 fake 进行断言,这意味着你将 fake 用作 mock,如果你只使用 fake 来运行测试而不对其进行断言,那么你将 fake 用作 stub。

                  【讨论】:

                  【解决方案21】:

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

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

                  所以,主要区别是:

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

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

                  【讨论】:

                  • 我认为你是对的,但这就是为什么 Fowler 的文章令人困惑,文章标题是“Mocks Aren't Stubs”......但它们是?! ¯_(ツ)_/¯
                  • @stonedauwg,确实,我编辑了我的帖子,加入了你的双关语和澄清。希望这会有所帮助。
                  • @stonedauwg,模拟不是存根,就像矩形不是正方形一样。 :)
                  【解决方案22】:

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

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

                  【讨论】:

                    【解决方案23】:

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

                    【讨论】:

                      【解决方案24】:

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

                      【讨论】:

                        【解决方案25】:

                        这张幻灯片很好地解释了主要区别。

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

                        【讨论】:

                        • 这是对两者之间差异的更清晰的解释,IMO。对于存根:测试人员获取存根并直接在被测类中使用它。但是对于 Mock,测试人员必须确定如何使用 Mock 对象。在不同的情况下,它的行为会有所不同。相比之下,存根的行为不会有所不同,而是按原样使用(意味着每次联系时都返回相同的数据)
                        【解决方案26】:

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

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

                        【讨论】:

                          【解决方案27】:

                          他使用的通用术语是测试替身(想想特技替身)。 Test Double 是一个通用术语,用于替换生产对象以进行测试的任何情况。 Gerard 列出了多种类型的 double:

                          • Dummy 对象被传递但从未实际使用过。通常它们只是用来填充参数列表。
                          • Fake 对象实际上有工作实现,但通常采用一些捷径,这使得它们不适合生产(InMemoryTestDatabase 就是一个很好的例子)。
                          • 存根为测试期间拨打的电话提供预设答案,通常根本不响应任何超出测试程序的内容。
                          • Spies 是存根,它还会根据调用方式记录一些信息。其中一种形式可能是电子邮件服务,它记录发送了多少条消息(也称为Partial Mock)。
                          • Mocks 预编程了期望,这些期望形成了他们期望接收的调用的规范。如果他们收到意外的呼叫,他们可能会抛出异常,并在验证过程中进行检查以确保他们收到了他们期待的所有呼叫。

                          Source

                          【讨论】:

                            【解决方案28】:

                            上面有很多有效的答案,但我认为值得一提的是鲍勃叔叔的这张表格: https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html

                            最好的例子解释!

                            【讨论】:

                            • 仅链接的答案在 SO 上不被视为有价值。
                            【解决方案29】:
                            • 存根与模拟
                              • 存根
                                1. 为方法调用提供具体答案
                                  • 例如:myStubbedService.getValues() 只返回被测代码所需的字符串
                                2. 被测试代码用来隔离它
                                3. 无法通过测试
                                  • 例如:myStubbedService.getValues() 只返回存根值
                                4. 经常实现抽象方法
                              • 模拟
                                1. 存根的“超集”;可以断言某些方法被调用
                                  • 例如:验证 myMockedService.getValues() 是否只被调用一次
                                2. 用于测试被测代码的行为
                                3. 测试失败
                                  • 例如:验证 myMockedService.getValues() 是否被调用过一次;验证失败,因为我的测试代码没有调用 myMockedService.getValues()
                                4. 经常模拟接口

                            【讨论】:

                              【解决方案30】:

                              让我们看看测试替身:

                              • Fake:Fake 是具有工作实现但与生产实现不同的对象。 :数据访问对象或存储库的内存实现。
                              • 存根:存根是一个保存预定义数据并在测试期间使用它来接听电话的对象。 :需要从数据库中抓取一些数据来响应方法调用的对象。

                              • Mocks:Mocks 是注册它们收到的调用的对象。 在测试断言中,我们可以在 Mocks 上验证所有预期的操作都已执行。 :调用邮件发送服务的功能。 更多信息请查看this

                              【讨论】:

                              • 我认为的最佳答案
                              猜你喜欢
                              • 1970-01-01
                              • 2011-11-08
                              • 2011-10-09
                              • 2012-11-21
                              • 1970-01-01
                              • 2011-10-09
                              • 1970-01-01
                              • 2011-01-11
                              相关资源
                              最近更新 更多