Unit testing 何时使用存根和模拟?

Unit testing 何时使用存根和模拟?,unit-testing,mocking,stubbing,Unit Testing,Mocking,Stubbing,我一直有这种困惑。如果我编写了一个使用伪代码来断言某个操作的代码,那么当真正开始使用真实对象而不是伪对象时,我如何信任真正的实现呢 例如,我有以下代码-- 这段代码测试“contact”对象的实现,不管它是否保存到数据库中。若我碰巧使用了一个存根而不是一个真正的数据库连接,我是否需要单独的测试来将它存储在数据库中?你们称之为集成测试吗 非常感谢您的回答。是的,根据您的定义,使用真正的数据库将更具功能性或集成测试。就我个人而言,我觉得单元测试应该只测试这个方法,而不测试其他任何东西。因此,无论会话

我一直有这种困惑。如果我编写了一个使用伪代码来断言某个操作的代码,那么当真正开始使用真实对象而不是伪对象时,我如何信任真正的实现呢

例如,我有以下代码--

这段代码测试“contact”对象的实现,不管它是否保存到数据库中。若我碰巧使用了一个存根而不是一个真正的数据库连接,我是否需要单独的测试来将它存储在数据库中?你们称之为集成测试吗


非常感谢您的回答。

是的,根据您的定义,使用真正的数据库将更具功能性或集成测试。就我个人而言,我觉得单元测试应该只测试这个方法,而不测试其他任何东西。因此,无论会话或事务是否工作,您的单元测试必须确保在必要时调用这些对象来完成它们的工作——这就是mock和stub的作用。您使用它们来确保您的单元测试与外部功能分离,以便可以将其作为基本单元进行测试;无论如何,理想情况下。

您应该测试您编写的代码。如果您编写了数据库连接对象代码,请对其进行测试。否则,如果它是带有自己测试的库的一部分,您可以对它进行模拟/存根,并假设如果连接对象通过了自己的测试套件,那么它就可以工作


例如,我不会测试对Hibernate方法的调用,我假设Hibernate开发人员已经彻底测试过了。但是我会测试我是否调用了正确的方法,使用模拟来设置期望值。

当您只想让函数返回某个值(或不执行任何操作)时,使用存根。您并不真正关心函数是否被调用,您只想隔离一些东西

mock功能更强大,因为您还可以跟踪函数是否被调用,调用次数,甚至使用函数得到的值执行操作


在您的例子中,如果您想要模拟数据库(因此它成为一个单元测试而不是一个功能测试),您可以模拟ISession和ITransaction。然后,您可以将这些值存储在内存中,并检查是否保存了正确的值。

大多数类型的单元测试都是关于测试单个代码段的,存根和模拟只是帮助您逐段测试的工具。通过单独测试工件,您可能可以对每个工件进行更详细的测试,但不能保证对整个画面有任何了解。各种各样的集成测试都可以做到这一点


在测试更大的功能时,我经常发现数据库逻辑的实际集成测试是最没有价值的测试工件,因为您通常会在UI级别测试这些相同的操作。

Martin Fowler有一个很好的讨论

从他的文章中:

Meszaros使用术语Test Double作为任何类型的假装对象的通用术语,用于代替真实对象进行测试。这个名字来源于电影中特技替身的概念。(他的目的之一是避免使用任何已经广泛使用的名称。)梅萨罗斯随后定义了四种特殊的双重身份:

  • 虚拟对象被传递,但从未实际使用过。通常它们只是用来填充参数列表
  • 伪对象实际上有工作实现,但通常采用一些快捷方式,这使得它们不适合生产(内存中的数据库就是一个很好的例子)
  • 存根为测试过程中拨打的电话提供固定的答案,通常对测试程序之外的任何内容都没有响应。存根还可以记录有关呼叫的信息,例如电子邮件网关存根,它可以记住它“发送”的消息,或者可能只记录它“发送”的消息数
  • 模拟就是我们在这里讨论的东西:预先编程的对象,带有期望值,这些期望值形成了它们期望接收的调用的规范

在这类双重测试中,只有模拟测试坚持进行行为验证。

能否用一些例子来详细说明你的答案。。提前感谢这是一个很长的答案,但是请看(为插头道歉)。
    [Test]
    public void CanCreateContactsWithData()
    {
        using(ISession session = factory.OpenSession())
        using (ITransaction trans = session.BeginTransaction())
        {
            _contactId = (long) session.Save(contact);
            trans.Commit();
        }

        Assert.AreNotEqual(0, _contactId);
    }