19

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

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

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

4

6 回答 6

10

回复中的沮丧是压倒性的......但不要害怕,我们有一本可以驱除遗留 C++ 代码恶魔的圣书。如果您与遗留 C++ 代码较量超过一周,请认真购买这本书。

转到第 127 页:可怕的包含依赖关系的案例。(现在我什至不在迈克尔·费瑟斯的英里范围内,但在这里我可以尽可能短地回答..)

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

现在说“在测试工具下实例化 ClassA 很困难”是轻描淡写的。(引用 MF 的示例 - 调度程序是我们的海报问题孩子,拥有大量的部门。)

#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 中被调用。
Blow#2 Fake-it-til-you-make-it(感谢 RonJ):

#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 练习:它可能并不总是那么简单.. 但你明白了。在前几次决斗之后,破坏部门的过程将变得容易-n-机械

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

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

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

更多..请读这本书。无价。战哥!

于 2008-09-15T19:22:35.633 回答
1

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

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

于 2008-09-15T17:53:15.737 回答
1

我没有直接回答你的问题,但如果你使用大量遗留代码,我担心单元测试可能不是要做的事情。

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

我试图找到一种将单元测试添加到应用程序的方法,但最后只是陷入了 catch-22:

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

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

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

于 2008-09-15T17:58:01.687 回答
1

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

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

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

于 2008-09-15T18:24:00.047 回答
0

如果您继续编写 stubs/mock/fake 代码,您可能会对具有不同行为的类进行单元测试,然后在主项目上编译。

但是,如果这些包含存在并且没有添加行为,那么没关系。

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

于 2008-09-15T17:57:56.210 回答
0

对于具有大量依赖项的遗留代码,您肯定处于困境和困境之间。你还有很长的路要走才能解决所有问题。

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

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

于 2008-09-15T18:09:34.420 回答