【问题标题】:Rhino Mocking and TDD with legacy code带有遗留代码的 Rhino Mocking 和 TDD
【发布时间】:2010-08-19 08:33:10
【问题描述】:
首先让我说我正在使用遗留代码。因此,可以进行一些更改,但不能进行大刀阔斧的更改。
我的问题是我有一个“车辆”对象,它非常简单,但上面没有接口或任何东西。这个项目是在 TDD 真正开始成为主流之前创建的。无论如何,我需要添加一个新方法来更改车辆的启动里程。我认为这将是尝试 TDD 和 Mocking 的良好开端,因为我是新手。我的问题是我需要创建一个车辆做一些涉及到数据库的检查。抱歉,如果我的问题不是 100% 清楚,这就是我发帖的原因,因为我对 Rhino Mocks 适合的位置有点困惑(如果我需要的话!)。
【问题讨论】:
标签:
unit-testing
dependency-injection
mocking
【解决方案1】:
问题在于依赖关系。您的车辆类别取决于数据库。希望所有与数据库的交互都被封装到一个很好的类中,我们稍后会回到这个类。当您启动单元测试时,您希望能够测试车辆类而不必关心数据库。例如,您想检查您的 SpeedUp(int x) 方法是否确实将总速度提高了 x。在这种方法中,它做的第一件事就是询问数据库当前的速度。这意味着你必须有一个数据库来测试! Dam,这听起来不像是一个非常快速的测试,也不是可重复的。还有很多设置来运行测试。
如果我们可以有一个假数据库不是很好吗?这就是模拟的用武之地。我们创建一个封装了所有数据库交互的类的模拟。然后,我们将模拟设置为使用预先编程的值进行响应。因此,例如,当我们向数据库询问当前速度时,您返回 100。
所以现在当我们测试模拟返回 100 时,我们可以断言 SpeedUp(int x) 需要 100 并将 x 添加到它。
【解决方案2】:
Rhino 模拟只能从接口或抽象类创建模拟,它们不存在您的遗留代码。
TypeMock 可以模拟任何东西,但不是免费的。
您可以使用Microsoft Moles 来模拟这些。
但是,您应该考虑到 Moles 应该是您最后的解决方案,最好重构您的代码并通过从业务层中抽象出您的数据层来使其可测试。
HTH
【解决方案3】:
创建 Vehicle 类型的实例(对象)然后调用您的方法进行测试是否容易?如果是,那么您可能不需要模拟。
但是,如果您的 Vehicle 类型具有执行您要测试的操作所需的依赖项(如数据库访问对象),那么您希望使用一个模拟数据库访问对象,该对象返回测试的预设值,因为您希望您的单元测试能够快速运行。
Vehicle [depends On>] OwnerRepository [satisfied By] SQLOwnerRepository
因此,您引入了一个接口(例如,一个 OwnerRepository 来获取所有者的详细信息)来分离两者之间的 DB 交互(定义合同)。让你真正的依赖(这里是 SQLOwnerRepository)实现这个接口。还要设计您的代码,以便可以注入依赖项,例如
public Vehicle (OwnerRepository ownerRepository)
{ _ownerRepository = ownerRepository; // cache in member variable }
现在在测试代码中,
Vehicle [depends On >] OwnerRepository [satisifed By] MockOwnerRepository
你的框架给定一个接口会创建它的模拟实现(参见 Rhino/Moq 框架)。因此,您不再需要实际的数据库连接来测试您的 Vehicle 类。 Mocks 用于抽象出耗时/不可控的依赖项,以使您的单元测试保持快速/可预测。
我建议阅读“依赖注入”以更好地了解何时以及为何使用模拟。
【解决方案4】:
不能直接回答您的问题。但是,值得看看以下内容。
Gabriel Schenker 发布了关于在遗留系统中应用 TDD 的文章。 PTOM – Brownfield development – Making your dependencies explicit
这篇文章解释了关于制作dependencies explicit 和使用依赖注入。它还告诉我们Poor Man’s Dependency Injection。当只有默认构造函数时需要这样做。
类似
public OrderService() : this(
new OrderRepository(),
new EmailSender(ConfigurationManager.AppSettings["SMTPServer"])
)
本文还涉及为 ConfigurationManager 创建一个包装器以使其可测试。