【发布时间】:2010-11-27 16:39:31
【问题描述】:
在最近的一次采访中,有人问我为什么要创建模拟对象。我的回答是这样的,“拿一个数据库——如果你正在编写测试代码,你可能不希望该测试实时连接到将执行实际操作的生产数据库。”
从回复来看,我的答案显然不是面试官想要的。有什么更好的答案?
【问题讨论】:
标签: unit-testing mocking testing
在最近的一次采访中,有人问我为什么要创建模拟对象。我的回答是这样的,“拿一个数据库——如果你正在编写测试代码,你可能不希望该测试实时连接到将执行实际操作的生产数据库。”
从回复来看,我的答案显然不是面试官想要的。有什么更好的答案?
【问题讨论】:
标签: unit-testing mocking testing
我会这样总结:
在一次采访中,我建议在开发人员使用 依赖注入 时加入 mock,因为它可以让您拥有更多控制权,并且更轻松地构建测试。
【讨论】:
采用稍微不同的方法(因为我认为上面已经很好地介绍了模拟):
“获取一个数据库——如果您正在编写测试代码,您可能不希望该测试实时连接到将执行实际操作的生产数据库。”
IMO 说明示例使用的一种不好的方式。在使用或不使用模拟的测试期间,您永远不会“将其连接到产品数据库”。每个开发人员都应该有一个开发人员本地数据库来进行测试。然后您将继续使用测试环境数据库,然后可能是 UAT,最后是 prod。您不是为了避免使用实时数据库而进行模拟,而是为了使不直接依赖于数据库的类不需要您设置数据库。
最后(我接受我可能会在这方面得到一些 cmets)IMO 开发人员本地数据库是在单元测试期间命中的有效对象。您应该只在测试直接与数据库交互的代码时使用它,并在测试间接访问数据库的代码时使用模拟。
【讨论】:
我将在这里采取不同的方向。 Stubbing/Faking 做了上面提到的所有事情,但也许面试官认为 mock 是一个伪造的对象,会导致测试通过或失败。我基于xUnit terminology。这可能会引发一些关于state testing verses behavior / interaction testing 的讨论。
他们可能一直在寻找的答案是:模拟对象与存根不同。存根模拟被测方法的依赖关系。存根不应导致测试失败。模拟会执行此操作并且还会检查调用方式和时间。模拟根据潜在行为导致测试通过或失败。这样做的好处是在测试期间减少了对数据的依赖,但将其与方法的实现更紧密地联系在一起。
当然这是猜测,他们更可能只是想让您描述存根和 DI 的好处。
【讨论】:
这里是a few situations where mocking is indispensable:
【讨论】:
只是为了补充这里的好答案,模拟对象用于自上而下或自下而上的结构化编程(OOP 也是如此)。它们用于为上层模块(GUI、逻辑处理)提供数据或充当模拟输出。
考虑自上而下的方法:您首先开发一个 GUI,但 GUI 应该有数据。因此,您创建了一个模拟数据库,它只返回一个 std::vector 数据。您已经定义了关系的“合同”。谁在乎数据库对象内部发生了什么——只要我的 GUI 列表得到一个 std::vector 我就很高兴。这可以提供模拟用户登录信息,无论您需要什么让 GUI 正常工作。
考虑一种自下而上的方法。您编写了一个解析器,它读取分隔的文本文件。你怎么知道它是否有效?您为这些对象编写一个模拟“数据接收器”并将数据路由到那里以验证(尽管通常)数据是否被正确读取。上一层的模块可能需要 2 个数据源,但你只写了一个。
在定义模拟对象的同时,您还定义了合同关系的方式。这通常用于测试驱动编程。您编写测试用例,使用模拟对象使其工作,并且通常模拟对象的接口成为最终接口(这就是为什么在某些时候您可能希望将模拟对象的接口分离成纯抽象类) .
希望对你有帮助
【讨论】:
在进行单元测试时,每个测试都旨在测试单个对象。然而,系统中的大多数对象都会有其他与之交互的对象。模拟对象是这些其他对象的虚拟实现,用于隔离被测对象。
这样做的好处是任何失败的单元测试通常会将问题隔离到被测对象。在某些情况下,问题出在模拟对象上,但这些问题应该更容易识别和修复。
也可以为模拟对象编写一些简单的单元测试。
它们通常用于创建模拟数据访问层,以便可以在与数据存储隔离的情况下运行单元测试。
其他用途可能是在 MVC 模式中测试控制器对象时模拟用户界面。这允许更好地自动化测试可以在某种程度上模拟用户交互的 UI 组件。
一个例子:
public interface IPersonDAO
{
Person FindById(int id);
int Count();
}
public class MockPersonDAO : IPersonDAO
{
// public so the set of people can be loaded by the unit test
public Dictionary<int, Person> _DataStore;
public MockPersonDAO()
{
_DataStore = new Dictionary<int, Person>();
}
public Person FindById(int id)
{
return _DataStore[id];
}
public int Count()
{
return _DataStore.Count;
}
}
【讨论】:
在团队中工作时,模拟对象/函数也很有用。如果您正在处理代码库的一部分,该部分代码库依赖于其他人负责的代码库的不同部分——仍在编写或尚未编写——模拟对象/函数在给你一个预期的输出,这样你就可以继续你的工作,而不必等待其他人完成他们的工作。
【讨论】: