我刚刚开始使用 #include 指令为具有大量物理依赖性的遗留代码模块编写单元测试。我一直在用一些感觉过于乏味的方法来处理它们(提供空头来打破长#include依赖项列表,并使用#define来防止类被编译),并且正在寻找一些更好的策略来处理这些问题。

我经常遇到这样的问题:用空白版本复制几乎每个头文件,以便将我正在测试的类完整地分开,然后为需要的对象编写大量的存根/模拟/假代码。被替换,因为它们现在未定义。

有人知道一些更好的做法吗?

有帮助吗?

解决方案

回复中的压抑感实在是太强烈了……但别害怕,我们有 驱除遗留 C++ 代码恶魔的圣书. 。如果您已经与遗留的 C++ 代码进行了长达一周的较量,那么真的请购买这本书。

翻到第127页: 可怕的情况包括依赖性。 (现在我距离迈克尔·费瑟斯还不到几英里,但这里是我能回答的最短的地方……)

问题:在 C++ 中,如果 classA 需要了解 ClassB,则 Class B 的声明将直接提升/文本包含在 ClassA 的源文件中。由于我们程序员喜欢将其带到错误的极端,因此一个文件可以递归地包含无数个其他文件。构建需要数年时间..但嘿至少它建立了..我们可以等待。

现在说“在测试工具下实例化 ClassA 很困难”是轻描淡写的说法。(引用 MF 的例子 - Scheduler 是我们的海报问题孩子,有大量的 deps。)

#include "TestHarness.h"
#include "Scheduler.h"
TEST(create, Scheduler)     // your fave C++ test framework macro
{
  Scheduler scheduler("fred");
}

这将导致包含龙出现一系列构建错误。
Blow#1 耐心和坚持:一次处理每个包含项,然后决定我们是否真的需要这种依赖关系。假设 SchedulerDisplay 是其中之一,它的 displayEntry 方法在 Scheduler 的 ctor 中调用。
吹#2 假装直到你成功 (谢谢罗恩):

#include "TestHarness.h"
#include "Scheduler.h"
void SchedulerDisplay::displayEntry(const string& entryDescription) {}
TEST(create, Scheduler)
{
  Scheduler scheduler("fred");
}

pop 是依赖项及其所有传递性包含项。您还可以通过将 Fakes 方法封装在要包含在测试文件中的 Fakes.h 文件中来重用 Fake 方法。
吹气#3 练习:事情可能并不总是那么简单..但你明白了。在最初的几次决斗之后,打破部门的过程将变得简单而机械

注意事项 (我有没有提到有一些警告?:)

  • 我们需要为该文件中的测试用例单独构建;我们在程序中只能有 1 个 SchedulerDisplay::displayEntry 方法的定义。因此,为调度程序测试创建一个单独的程序。
  • 我们不会破坏程序中的任何依赖关系,因此我们不会使代码更简洁。
  • 只要我们需要测试,您就需要保留这些假货。
  • 你的审美观可能会被冒犯一段时间..咬紧嘴唇,“忍受我们,共创美好明天”

对于具有严重依赖性问题的非常大的类,请使用此技术。不要经常或轻微使用.. 以此作为更深入重构的起点。 随着时间的推移,当您提取更多类(带有他们自己的测试)时,这个测试程序可以在谷仓后面进行。

对于更多..请一定要读这本书。无价。战斗吧兄弟!

其他提示

由于您正在测试遗留代码,我假设您无法重构所述代码以减少依赖性(例如通过使用 粉刺成语)

恐怕这让你别无选择。类型或函数包含的每个标头都需要该类型或函数的模拟对象才能编译所有内容,您无能为力......

我不会直接回答您的问题,但我担心如果您使用大量遗留代码,单元测试可能不适合做。

在领导 XP 团队完成一个新开发项目后,我真的很喜欢我的单元测试。事情发生了,几年后,我发现自己正在处理一个存在很多质量问题的大型遗留代码库。

我试图找到一种向应用程序添加单元测试的方法,但最终陷入了第 22 条军规:

  1. 为了编写有意义的完整单元测试,需要重构代码。
  2. 如果没有单元测试,重构代码将非常危险。

如果您觉得自己像个英雄,并且在单元测试中喝了一杯清凉饮料,那么您仍然可以尝试一下,但存在真正的风险,即您最终会得到更多现在也需要维护的几乎没有价值的测试代码。

有时,最好以“设计”的方式处理代码。

我不知道这是否适用于您的项目,但您可能会尝试从 链接阶段 你的构建。

这将完全消除您的#include 问题。您需要做的就是重新实现包含文件中的接口来执行您想要的操作,然后链接到您创建的模拟对象文件以实现包含文件中的接口。

这种方法的一大缺点是构建系统更加复杂。

如果您继续编写存根/模拟/假代码,您可能会冒着对具有与在主项目上编译时不同行为的类进行单元测试的风险。

但如果这些包含存在并且没有添加行为,那么就可以了。

在进行单元测试时,我会尝试不更改包含内容中的任何内容,这样您就可以确定(就遗留代码而言:))您测试的是真实代码。

对于具有大量依赖项的遗留代码,您肯定陷入进退两难的境地。要解决这一切,你还有很长的路要走。

从您所说的来看,您似乎试图依次保持每个模块的源代码完整,将其放置在模拟了外部依赖项的测试工具中。我的建议是采取更勇敢的步骤,尝试进行一些重构来消除(或 倒置)依赖关系,这可能正是您想要避免的步骤。

我建议这样做是因为我猜测当你编写测试时依赖项会杀死你。如果您能够消除依赖性,从长远来看,您肯定会过得更好。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top