C ++单元测试遗留代码:如何处理#include?

时间:2008-09-15 17:47:54

标签: c++ unit-testing legacy

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

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

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

6 个答案:

答案 0 :(得分:9)

反应中的沮丧压倒性......但不要害怕,我们已经the holy book to exorcise the demons of legacy C++ code。如果你与传统的C ++代码进行了超过一周的争夺,那么请认真购买这本书。

转到第127页:可怕的情况包括依赖性。(现在我甚至不在迈克尔羽毛的数英里之内,但在这里,我可以管理答案的简短... )

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

现在说'在测试工具下实例化ClassA很困难'是轻描淡写的。 (引用MF的例子 - 调度程序是我们的海报问题孩子与deps嘉豪。)

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

这将带来一系列构建错误的包含龙 打击#1 Patience-n-Persistence :每次包含一个,并确定我们是否真的需要这种依赖。我们假设SchedulerDisplay就是其中之一,在调度程序的ctor中调用其displayEntry方法。
打击#2伪造它直到你让它(感谢RonJ):

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

pop和依赖包含所有的传递。 您还可以通过将Fake方法封装在Fakes.h文件中来重用Fake方法,以包含在测试文件中 打击#3练习:可能并不总是那么简单......但是你明白了。在最初的几次决斗之后,打破deps的过程将变得容易 - 机械

警告(我是否提到过警告?:)

  • 我们需要在此文件中为测试用例单独构建;我们在程序中只能有一个SchedulerDisplay :: displayEntry方法的定义。因此,为调度程序测试创建一个单独的程序。
  • 我们没有破坏程序中的任何依赖项,因此我们不会使代码更清晰。
  • 只要我们需要测试,你就需要保持这些假货。
  • 你的审美观可能会被冒犯一段时间......只是咬住你的嘴唇,'忍受我们的美好明天'

将此技术用于具有严重依赖性问题的非常庞大的类。不要经常使用或轻易使用.. 将此作为更深层重构的起点。随着时间的推移,这个测试程序可以在谷仓后面提取更多类(使用他们自己的测试)。 / p>

更多..请阅读本书。无价。打击兄弟!

答案 1 :(得分:1)

由于您正在测试遗留代码,我假设您无法重构所述代码以减少依赖性(例如,使用pimpl idiom

这让我很害怕。为类型或函数包含的每个头文件都需要一个模拟对象用于该类型或函数以便编译所有内容,您可以做的很少...

答案 2 :(得分:1)

我没有直接回答您的问题,但如果您处理大量遗留代码,我担心单元测试可能不是您应该做的事情。

在绿色领域开发项目中领导XP团队后,我非常喜欢我的单元测试。事情发生了,几年后我发现自己正在研究一个存在很多质量问题的大型遗留代码库。

我试图找到一种方法将单元测试添加到应用程序中,但最终只是卡在了一个catch-22中:

  1. 为了写出完整的单元测试意义,代码需要重构。
  2. 如果没有单元测试,重构代码就太危险了。
  3. 如果你觉得自己像个英雄并且在单元测试中喝了冷却辅助,那么你仍然可以尝试一下,但是你真的有一个真正的风险,你最终会得到一些价值不大的测试代码,现在也需要保持。

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

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:0)

如果你继续编写存根/模拟/假代码,你就有可能在具有不同行为的类上进行单元测试,然后在主项目上进行编译。

但是如果那些包含在那里并且没有添加行为那么它就是好的。

在进行单元测试时,我不会尝试更改包含内容,因此您可以确定(至于您可以使用遗留代码:))您正在测试实际代码。

答案 5 :(得分:0)

对于具有大型依赖关系的遗留代码,您肯定处于摇滚和困难之间。你需要长时间艰苦的努力才能彻底解决这个问题。

根据您的说法,您似乎正在尝试依次为每个模块保持源代码的完整性,将其置于具有外部依赖关系模拟的测试工具中。我的建议是采取更加勇敢的步骤,尝试进行一些重构以消除(或invert)依赖关系,这可能是你试图避免的步骤。

我建议这是因为我猜测依赖会在你编写测试时杀了你。如果你能消除依赖关系,你肯定会在长期内变得更好。