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

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

C++ Unit Testing Legacy Code: How to handle #include?

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

我经常遇到这样的问题:几乎将每个头文件都复制为空白版本,以完整地分离出我正在测试的类,然后为需要被处理的对象编写大量的stub / mock / fake代码。 已替换,因为它们现在不

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


响应中的压抑感是压倒性的...但是,不用担心,我们拥有一本神圣的书,可以驱除遗留C ++代码的魔鬼。如果您排队使用传统C ++代码超过一个星期,请认真购买本书。

转到第127页:可怕的包含依赖项。 (现在,我什至不在迈克尔·费瑟斯(Michael Feathers)的距离之内,但在这里我可以尽我所能地回答。。)

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

现在说"很难在测试工具下实例化ClassA"是轻描淡写的。 (引用MF的示例-Scheduler是我们的海报问题孩子,拥有丰富的经验。)

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

这将带出一系列错误的包含龙。
Blow#1 Patience-n-Persistence:一次接受每个包含项,并确定我们是否真的需要该依赖项。假设SchedulerDisplay是其中之一,其displayEntry方法在Scheduler的ctor中被调用。
打击#2假装直到制作成功(感谢RonJ):

1
2
3
4
5
6
7
#include"TestHarness.h"
#include"Scheduler.h"
void SchedulerDisplay::displayEntry(const string& entryDescription) {}
TEST(create, Scheduler)
{
  Scheduler scheduler("fred");
}

而pop依赖项及其所有可传递的包含项。
您也可以通过将Fake方法封装在Fakes.h文件中以包含在测试文件中来重用Fake方法。
Blow#3练习:可能并不总是那么简单..但是您明白了。在前几场决斗之后,打破深度的过程将变得轻松而机械

警告(我提到有警告吗?:)

  • 我们需要在此文件中为测试用例建立单独的版本;我们在程序中对SchedulerDisplay :: displayEntry方法只能有1个定义。因此,为调度程序测试创建一个单独的程序。
  • 我们不会破坏程序中的任何依赖关系,因此不会使代码更简洁。
  • 只要我们需要测试,就需要维护那些假货。
  • 您的审美意识可能会冒犯一段时间。..咬住嘴唇,"忍受我们,共创美好的明天"

将此技术用于具有严重依赖问题的非常庞大的类。不要经常使用或轻描淡写。。以此为基础进行更深入的重构。随着时间的推移,当您提取更多的类时(通过他们自己的测试),可以将该测试程序带到谷仓后面。

有关更多信息,请阅读本书。无价。加油!


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

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

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


我不是直接回答您的问题,但是我担心如果您使用大量的旧代码,就不能做单元测试。

在带领XP团队进行绿色开发项目之后,我真的很喜欢我的单元测试。事情发生了,几年后,我发现自己正在处理大量质量问题的大型遗留代码库。

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

  • 为了编写含义完整的单元测试,将需要重构代码。
  • 没有单元测试,重构代码将太危险。
  • 如果您觉得自己像个英雄,并且在单元测试中喝了些辅助工具,那么您仍然可以尝试一下,但是确实有风险,您最终只能获得更多价值不大的测试代码,现在还需要维护这些代码。

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


    由于您正在测试旧版代码,因此我假设您无法重构所述代码以减少依赖(例如,使用pimpl习惯用法)

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


    拥有大量依赖关系的遗留代码绝对会让您陷入困境。您需要花很长时间才能解决所有问题。

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

    我建议这样做是因为我猜这些依赖会在您编写测试时杀死您。如果可以消除依赖关系,从长远来看,您一定会过得更好。


    如果您继续编写存根/模拟/伪代码,则可能要对行为不同的类进行单元测试,然后再对主项目进行编译。

    但是,如果其中包含这些内容并且没有其他行为,那就可以了。

    在进行单元测试时,我会尽量不更改include上的任何内容,因此可以确保(到目前为止,您可以使用旧版代码:))测试实际代码。


    推荐阅读