在工厂方法中测试 objects 的创建

Testing the creation of objects in a factory method

目前我正在编写一个 test-driven 项目,我坚持测试以下行为。

我得到了一个名为 Menu 的接口,可以通过 addEntry 方法向其中动态添加条目。还有另一个 class 包含 Menu object。我们称它为 MenuPresenter。当调用特定方法(例如 someAction(string title))时,MenuPresenter 应使用接收到的标题实例化一个 Entry object 并将其添加到 Menu object. Entry object 的实例化发生在 MenuPresenter.

的工厂方法中

测试的行为应该是WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu

但是我没有找到正确的方法来编写测试。我想出了两种主要的可能性来测试它,但实际上我不喜欢这两种可能性,因为(稍后)提到的缺点。

  1. 实现一个 MenuSpy 继承自 Menu,它有一个 getAddedEntry-Method。像这样你可以提取添加的 Entry 并检查 object
    的状态 EXPECT_TRUE(entry->getTitle() == title);

缺点: 要检查 Entry object 的状态,我必须使用 [= 扩展接口的 API 85=] 仅用于测试原因或使用 public 成员变量。这允许每个客户端访问 Entry 的每个实现的内部结构。因此,客户与内部结构耦合。

  1. 通过具有
    EntryFactory接口扩展系统 makeEntry(std::string title)-方法。像这样可以实现 EntryFactorySpy 并且可以检查是否使用正确的参数(标题)调用了 makeEntry 方法。在另一个测试中,我们可以实现一个 EntryFactoryStub 其中 returns 一个特定的 Entry object。然后我们可以检查(通过 MenuSpyMenuPresenter 是否已将从工厂接收到的条目添加到菜单中。然后在工厂的单元测试中测试 Entry object 的实例化。

缺点:因为我测试了工厂的makeEntry方法的调用,使用工厂创建条目的算法是固定的。该测试与 MenuPresenter 的内部结构紧密耦合。更改算法(例如,现在使用工厂方法会破坏测试,否则应用程序的预期行为会中断。

对于应用程序的行为来说,MenuPresenter 是否创建了 Entry 本身或者它是否使用 EntryFactory 应该不重要。这就是为什么我更喜欢第一种方式。但我不希望 Entry 的客户端仅仅因为测试原因而耦合到 Entry 的内部结构。

这只是我的问题的一个例子。实际上,条目不仅是用字符串创建的。它得到其他复杂的 objects 作为 shared_ptr。这是另一个原因,为什么我不想使用 public getter-methods。像这样可以从 Entry 中提取复杂的 object 并更改它(是的,我可以给出一个 const shared_ptr,但这对我来说似乎不是一个好习惯。)

问题是,有人知道解决我的问题的测试模式或解决方案吗?意思是测试是否将正确的 Entry object 添加到 Menuobject 而未与算法或 Entry 的内部结构紧密耦合?

我是一名 java 开发人员,我从来没有在 C++ 中使用过 TDD,但我也许可以像你上面提到的那样描述你想要测试的内容。

在经典 TDD 中,MenuPresenter 应该更像是一个 mini-integration 测试,正如 Martin Fowler 在 Test Isolation.

中所说的那样

In essence classic xunit tests are not just unit tests, but also mini-integration tests. As a result many people like the fact that client tests may catch errors that the main tests for an object may have missed, particularly probing areas where classes interact.

在经典 TDD 中,如果可能,使用真实的 objects,如果在测试 MenuPresenter 时使用真实的东西不方便,则使用 Test Double。所以问问自己,当在 Menu 中添加 Entry 时,会发生什么预期效果。测试 Menu 将添加标题 Entry 只是没有意义的,您应该测试在 Entry added.let 说添加 Entry 之后会发生什么行为显示为带标题的 MenuItem,因此您只需断言真实的 Menu object 是否显示具有预期标题的 MenuItem

事实上,WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu 测试已经通过它的名称公开了 someAction 实现细节,并且将测试耦合到实现 someAction,另一方面,当您更改算法时someAction 个测试将失败。您可以将测试添加为 displaysAnTitltedMenuItemInMenuWhenSomeActionIsCalledWithinATitle.

如您所见,测试无论如何都会与实现耦合。我们唯一能做的就是让测试与实现的耦合尽可能地降低,这样,当实现发生变化时,我们只需更改一点测试即可。

如果你觉得Menu比较笨拙,你可以用MenuSpy代替,然后在MenuSpy & [=31=里面写一些IntegrationContractTest ].如您所见,使用 MenuSpy 的测试与实现的耦合度比真正的 Menu 高一点,因为我们希望在 MenuSpy 中添加标题为 Entry 而不是添加标题 EntryMenu 的效果。

MenuSpy检查是否添加了标题Entry,例如:

someAction(title);

menuSpy->hasReceivedATitledEntry(title);

:您可能会在返回标题之前检查添加的Entry是否是getAddedEntryTitle方法中的CheckableEntry

someAction(title);

EXPECT_TRUE(menuSpy->getAddedEntryTitle() == title);