在工厂方法中测试 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
但是我没有找到正确的方法来编写测试。我想出了两种主要的可能性来测试它,但实际上我不喜欢这两种可能性,因为(稍后)提到的缺点。
- 实现一个
MenuSpy
继承自 Menu
,它有一个 getAddedEntry
-Method。像这样你可以提取添加的 Entry
并检查 object
的状态
EXPECT_TRUE(entry->getTitle() == title);
缺点: 要检查 Entry
object 的状态,我必须使用 [= 扩展接口的 API 85=] 仅用于测试原因或使用 public 成员变量。这允许每个客户端访问 Entry
的每个实现的内部结构。因此,客户与内部结构耦合。
- 通过具有
的EntryFactory
接口扩展系统
makeEntry(std::string title)
-方法。像这样可以实现 EntryFactorySpy
并且可以检查是否使用正确的参数(标题)调用了 makeEntry
方法。在另一个测试中,我们可以实现一个 EntryFactoryStub
其中 returns 一个特定的 Entry
object。然后我们可以检查(通过 MenuSpy
)MenuPresenter
是否已将从工厂接收到的条目添加到菜单中。然后在工厂的单元测试中测试 Entry
object 的实例化。
缺点:因为我测试了工厂的makeEntry
方法的调用,使用工厂创建条目的算法是固定的。该测试与 MenuPresenter
的内部结构紧密耦合。更改算法(例如,现在使用工厂方法会破坏测试,否则应用程序的预期行为会中断。
对于应用程序的行为来说,MenuPresenter
是否创建了 Entry
本身或者它是否使用 EntryFactory
应该不重要。这就是为什么我更喜欢第一种方式。但我不希望 Entry
的客户端仅仅因为测试原因而耦合到 Entry
的内部结构。
这只是我的问题的一个例子。实际上,条目不仅是用字符串创建的。它得到其他复杂的 objects 作为 shared_ptr。这是另一个原因,为什么我不想使用 public getter-methods。像这样可以从 Entry
中提取复杂的 object 并更改它(是的,我可以给出一个 const shared_ptr,但这对我来说似乎不是一个好习惯。)
问题是,有人知道解决我的问题的测试模式或解决方案吗?意思是测试是否将正确的 Entry
object 添加到 Menu
object 而未与算法或 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
而不是添加标题 Entry
后 Menu
的效果。
让MenuSpy
检查是否添加了标题Entry
,例如:
someAction(title);
menuSpy->hasReceivedATitledEntry(title);
或:您可能会在返回标题之前检查添加的Entry
是否是getAddedEntryTitle
方法中的CheckableEntry
。
someAction(title);
EXPECT_TRUE(menuSpy->getAddedEntryTitle() == title);
目前我正在编写一个 test-driven 项目,我坚持测试以下行为。
我得到了一个名为 Menu
的接口,可以通过 addEntry
方法向其中动态添加条目。还有另一个 class 包含 Menu
object。我们称它为 MenuPresenter
。当调用特定方法(例如 someAction(string title)
)时,MenuPresenter
应使用接收到的标题实例化一个 Entry
object 并将其添加到 Menu
object. Entry
object 的实例化发生在 MenuPresenter
.
测试的行为应该是WhenSomeActionIsCalledShouldAddAnEntryContainingTitleToMenu
但是我没有找到正确的方法来编写测试。我想出了两种主要的可能性来测试它,但实际上我不喜欢这两种可能性,因为(稍后)提到的缺点。
- 实现一个
MenuSpy
继承自Menu
,它有一个getAddedEntry
-Method。像这样你可以提取添加的Entry
并检查 object
的状态EXPECT_TRUE(entry->getTitle() == title);
缺点: 要检查 Entry
object 的状态,我必须使用 [= 扩展接口的 API 85=] 仅用于测试原因或使用 public 成员变量。这允许每个客户端访问 Entry
的每个实现的内部结构。因此,客户与内部结构耦合。
- 通过具有
的EntryFactory
接口扩展系统makeEntry(std::string title)
-方法。像这样可以实现EntryFactorySpy
并且可以检查是否使用正确的参数(标题)调用了makeEntry
方法。在另一个测试中,我们可以实现一个EntryFactoryStub
其中 returns 一个特定的Entry
object。然后我们可以检查(通过MenuSpy
)MenuPresenter
是否已将从工厂接收到的条目添加到菜单中。然后在工厂的单元测试中测试Entry
object 的实例化。
缺点:因为我测试了工厂的makeEntry
方法的调用,使用工厂创建条目的算法是固定的。该测试与 MenuPresenter
的内部结构紧密耦合。更改算法(例如,现在使用工厂方法会破坏测试,否则应用程序的预期行为会中断。
对于应用程序的行为来说,MenuPresenter
是否创建了 Entry
本身或者它是否使用 EntryFactory
应该不重要。这就是为什么我更喜欢第一种方式。但我不希望 Entry
的客户端仅仅因为测试原因而耦合到 Entry
的内部结构。
这只是我的问题的一个例子。实际上,条目不仅是用字符串创建的。它得到其他复杂的 objects 作为 shared_ptr。这是另一个原因,为什么我不想使用 public getter-methods。像这样可以从 Entry
中提取复杂的 object 并更改它(是的,我可以给出一个 const shared_ptr,但这对我来说似乎不是一个好习惯。)
问题是,有人知道解决我的问题的测试模式或解决方案吗?意思是测试是否将正确的 Entry
object 添加到 Menu
object 而未与算法或 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
而不是添加标题 Entry
后 Menu
的效果。
让MenuSpy
检查是否添加了标题Entry
,例如:
someAction(title);
menuSpy->hasReceivedATitledEntry(title);
或:您可能会在返回标题之前检查添加的Entry
是否是getAddedEntryTitle
方法中的CheckableEntry
。
someAction(title);
EXPECT_TRUE(menuSpy->getAddedEntryTitle() == title);