在工厂方法

时间:2017-03-20 16:00:00

标签: c++ unit-testing design-patterns tdd design-principles

目前我正在编写一个测试驱动的项目,并且我坚持测试以下行为。

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

enter image description here

测试的行为应为WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu

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

  1. 实施从MenuSpy继承的Menu,其中包含getAddedEntry - 方法。像这样你可以提取添加的Entry并检查对象的状态
    EXPECT_TRUE(entry->getTitle() == title);
  2. 缺点:要检查Entry对象的状态,我要么使用getter-Methods扩展接口的API,这些方法仅用于测试原因或使用公共成员变量。这允许每个客户端访问Entry的每个实现的内部结构。因此,客户与内部结构相结合。

    1. 通过EntryFactory界面扩展系统 makeEntry(std::string title) - 方法。像这样可以实现EntryFactorySpy并且可以检查是否使用正确的参数(标题)调用makeEntry - 方法。在另一个测试中,我们可以实现一个返回特定EntryFactoryStub对象的Entry。然后,我们可以检查(通过MenuSpyMenuPresenter是否已将收到的条目从工厂添加到菜单中。然后,在工厂的单元测试中测试Entry对象的实例化。
    2. 缺点:因为我测试了工厂makeEntry - 方法的调用,所以修复了使用工厂创建条目的算法。测试与MenuPresenter的内部结构紧密耦合。更改算法(例如,现在使用工厂方法将破坏测试,而不会导致应用程序的预期行为中断。

      对于应用程序的行为,如果MenuPresenter创建Entry本身,如果它使用EntryFactory,那么它应该是不重要的。这就是为什么我更喜欢第一种方式。但是,由于测试原因,我不希望Entry的客户端与Entry的内部结构耦合。

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

      问题是,是否有人知道测试模式或解决方案来解决我的问题?意味着测试是否将Entry对象添加到Menu对象而不是紧密耦合到算法或Entry的内部结构?

1 个答案:

答案 0 :(得分:0)

我是一名java开发人员,我从不在c ++中使用TDD,但我可以描述你想要测试的内容,如上所述。

在Classic TDD中,MenuPresenter应该更像是一个迷你集成测试,正如Martin Fowler在Test Isolation中所说的那样。

  

本质上,经典的xunit测试不仅仅是单元测试,还包括小型集成测试。因此,许多人喜欢这样的事实:客户端测试可能会捕获对象的主要测试可能错过的错误,特别是探测类交互的区域。

在经典TDD中尽可能使用真实对象,如果在测试MenuPresenter时使用真实对象,则使用Test DoubleEntry。因此,问问自己在Menu中添加Menu时会发生什么预期效果。测试Entry将添加标题Entry只是没有意义的,你应该测试添加Entry之后的行为是什么。小时说MenuItem添加了遗嘱显示为标题Menu,因此您只需声明真实MenuItem对象是否显示带有预期标题的WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu

实际上,someAction测试已经通过其名称公开someAction实现细节,并且当您更改{的算法时,另一方面将测试与实现someAction耦合。 {1}}测试将失败。您可以将测试添加为displaysAnTitltedMenuItemInMenuWhenSomeActionIsCalledWithinATitle

正如您所看到的,测试将以任何方式耦合到实现。我们唯一能做的就是尽可能将实现的测试耦合为 lower ,这样,我们只需在实现更改时更改一些测试。

如果您发现Menu很尴尬,可以改用MenuSpy,然后在MenuSpyMenuImpl内写一些IntegrationContractTestMenuSpy。正如您所看到的那样,使用Menu的测试与实际的Entry相比要高一些,因为我们希望在MenuSpy中添加标题Menu而不是Entry标题MenuSpy添加后Entry的效果。

someAction(title); menuSpy->hasReceivedATitledEntry(title); 检查是否添加了标题Entry,例如:

CheckableEntry

:您可能会在标题返回之前检查添加的getAddedEntryTitle someAction(title); EXPECT_TRUE(menuSpy->getAddedEntryTitle() == title); 方法中{{1}}是否为{{1}}。

{{1}}