首页 文章

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

提问于
浏览
722

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

30 回答

  • 1

    我喜欢Roy Osherove发表的解释[video link] .

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

  • 570

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

    enter image description here

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

  • 7

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

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

  • 2

    Stub

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

    Mock

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

    Difference

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

    Similarity

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

  • 4

    要非常清楚和实用:

    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!');
       }
    }
    
  • 2

    前言

    对象有几种定义,不是真实的 . 一般术语是 test double . 该术语包括: dummyfakestubmock .

    参考

    根据Martin Fowler's article

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

    风格

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

    原则

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

    生命周期

    使用存根测试生命周期:

    • 设置 - 准备正在测试的对象及其存根协作者 .

    • 练习 - 测试功能 .

    • 验证状态 - 使用断言检查对象的状态 .

    • 拆解 - 清理资源 .

    使用模拟测试生命周期:

    • 设置数据 - 准备正在测试的对象 .

    • Setup expectations - 准备主要对象正在使用的模拟期望 .

    • 练习 - 测试功能 .

    • Verify expectations - 验证是否已在mock中调用了正确的方法 .

    • 验证状态 - 使用断言检查对象的状态 .

    • 拆解 - 清理资源 .

    摘要

    模拟和存根测试都给出了问题的答案: What is the result?

    使用模拟测试也对以下内容感兴趣: How the result has been achieved?

  • 10

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

  • 3

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

    • Dummy - 只是为了满足 API 的假值 .

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

    • Fake - 创建一个类的测试实现,该实现可能依赖于某些外部基础结构 . (这是一个很好的做法,你的单元测试确实 NOT 实际上与外部基础设施互动 . )

    示例:创建用于访问数据库的虚假实现,将其替换为内存中集合 .

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

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

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

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

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

  • 2

    codeschool.com课程中,Rails Testing for Zombies,他们给出了这个定义条款:

    存根

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

    嘲笑

    一个存根,断言该方法被调用 .

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

  • 13

    存根不会使测试失败,模拟可以 .

  • 17

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

    告诉我们处理存根的最简单方法是注意存根永远不会使测试失败 . 断言测试用途总是针对被测试的类 . 另一方面,测试将使用模拟对象来验证测试是否失败 . [...]同样,模拟对象是我们用来查看测试是否失败的对象 .

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

  • 2

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

    让我试着在 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:我没有't written any of my UI methods yet, and I'我不知道我需要为我的ui对象编写什么方法 . 我希望通过编写测试代码来了解我的控制器如何与我的ui对象协作 .

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

    如果您想了解更多有关模拟的信息,我建议您阅读本文:http://jmock.org/oopsla2004.pdf

  • 687

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

    Apple的方式是什么意思?

  • 3

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

    • Stub :一段允许测试运行的虚拟代码,但你不关心它发生了什么 .

    • Mock :一段虚拟代码,作为测试的一部分,您可以正确调用VERIFY .

    • Spy :一段虚拟代码,拦截对真实代码的一些调用,允许您在不替换整个原始对象的情况下验证调用 .

  • 9

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

    Stub就像确保方法返回正确的值一样,Mock就像实际进入方法并确保内部的所有内容在返回正确的值之前是正确的 .

  • 178

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

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

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

    What does test run smoothly mean?
    例如下面的代码:

    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类的结果 .

  • 1
    • Stubs vs. Mocks

    • 存根

    • 提供方法调用的具体答案

    • ex:myStubbedService.getValues()只返回被测代码所需的字符串

    被测试的代码使用

    • 来隔离它

    • 不能失败测试

    • ex:myStubbedService.getValues()只返回存根值

    • 经常实现抽象方法

    • 模拟

    • "superset"存根;可以断言某些方法被调用

    • ex:验证myMockedService.getValues()仅被调用一次

    • 用于测试被测代码的行为

    • 可以通过测试失败

    • ex:验证myMockedService.getValues()被调用一次;验证失败,因为我的测试代码没有调用myMockedService.getValues()

    • 经常嘲笑接口

  • 3

    正是来自论文Mock Roles, not Objects,由jMock的开发人员:

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

    所以,主要的区别是:

    在存根上设置的

    • 期望通常是通用的,而对模拟设置的期望可以更多_(例如,在第一次调用时返回此值,在第二次调用时返回此值等) .

    • stubs 主要用于 setup indirect inputs of the SUT ,而 mocks 可用于测试SUT的 both indirect inputs and indirect 输出 .

    总而言之,同时也试图驱散Fowler's article Headers 的混淆: mocks are stubs, but they are not only stubs .

  • 3

    使用 mental model 确实帮助我理解了这一点,而不是所有的解释和文章,这些并不完全"sink in" .

    想象一下,你的孩子 table 上有一个玻璃盘,他开始玩它 . 现在,你担心它会破裂 . 所以,你给他一块塑料板 . 那将是 Mock (相同的行为,相同的接口,"softer"实现) .

    现在,假设您没有't have the plastic replacement, so you explain 663599 . That' s Stub ,您提前提供了预定义状态 .

    Dummy 将是他甚至没有使用的叉子......而 Spy 可能就像提供你已经使用的相同解释一样有效 . 如果他继续(不确定最后一部分:),警察就会说谎,就像说谎一样 .

  • 6

    请参阅下面使用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";
            }
        }
    }
    
  • 161

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

  • 0

    Stub和Mock测试观点:

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

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

  • 251

    让我看看测试双打:

    • Fake :伪造是具有有效实现的对象,但与生成实现不同 . Such as :数据访问对象或存储库的内存实现 .

    • Stub :Stub是一个保存预定义数据的对象,用于在测试期间应答呼叫 . Such as :需要从数据库中获取一些数据以响应方法调用的对象 .

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

  • 13

    我在答案中使用了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"
    

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

    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的例子编写类似的书,那将会很棒 . 如果有人知道这样的书,请分享 . 干杯:)

  • 8

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

  • 15

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

    function foo(){}
    

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

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

    在断言和状态方面:

    • 在事件或状态更改之前声明了模拟

    • 存根未被断言,它们在事件之前提供状态以避免从不相关的单元执行代码

    • Spy 设置为存根,然后在事件或状态更改后断言

    • 伪造未被断言,它们在具有硬编码依赖性的事件之后运行以避免状态

    References

  • 17

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

    有例子的最佳解释!

  • 112

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

    单元测试 - >存根

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

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

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

    A Mock is like a stub, only it also has methods that make it possible determine what methods where called on the Mock . 因此,使用模拟可以测试单元是否可以正确处理各种返回值,以及单元是否正确使用协作者 . 例如,您无法通过dao对象返回的值查看是否使用Statement或PreparedStatement从数据库读取数据 . 你也不能看到在返回值之前是否调用了connection.close()方法 . 这可以通过模拟实现 . 换句话说,模拟可以测试单元与协作者的完整交互 . 不只是返回单位使用的值的协作者方法 . 在单元测试中使用模拟可以表达如下:

    单元测试 - >模拟

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

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

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

    更多详情>> Here

  • 26

    测试对象响应于某些提示(函数调用)或其他刺激而执行动作 . 以下是测试情况的具体示例 .

    场景 - EMT学生考试

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

    为了测试目的,找到患有各种疾病的患者太昂贵了 . 相反,我们使用演员 . 我们问测试对象(Ian)“你到达现场并且患者被固定并且失去知觉你先做什么?”伊恩回应“我检查场景是否安全” . 测试指导员说“场景安全” .

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

    Here, the instructor (and actor) are a mock. 医学培训使用与计算机科学家相同的术语(例如模拟代码模拟) .

    场景 - 注册网站

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

    该网站要求您年满21岁 . 因此,您输入1970年1月1日的值 . 它符合要求,它使您免于实现记住我的生日和类型输入工作流程的繁琐过程 .

    This date is a stub. 此单词用法特定于计算机科学 .

  • 2

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

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

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

相关问题