C ++单元测试旧版代码:如何处理#include?
我刚刚开始使用#include指令为具有较大物理依赖性的旧版代码模块编写单元测试。我一直在与他们打交道,觉得有些乏味(提供空标头打破长的#include依赖关系列表,并使用#define防止类被编译),并且正在寻找一些更好的策略来处理这些问题。
我经常遇到这样的问题:几乎将每个头文件都复制为空白版本,以完整地分离出我正在测试的类,然后为需要被处理的对象编写大量的stub / mock / fake代码。已替换,因为它们现在不确定。
有人知道一些更好的做法吗?
解决方案
回答
由于我们正在测试旧版代码,因此我假设我们无法重构所述代码以减少依赖(例如,使用pimpl习惯用法)
恐怕我们别无选择。类型或者函数包含的每个标头都需要该类型或者函数的模拟对象才能编译所有内容,我们几乎无能为力。
回答
如果我们继续编写存根/模拟/伪造代码,则可能会冒险对具有不同行为的类进行单元测试,然后再在主项目上进行编译。
但是,如果其中包含这些内容并且没有其他行为,那就可以了。
在进行单元测试时,我会尽量不更改include上的任何内容,以确保(到目前为止,我们可以使用旧版代码:))测试实际代码。
回答
我不是直接回答问题,但恐怕如果我们要处理大量的旧代码,则单元测试可能就不行了。
在带领XP团队进行绿色开发项目之后,我真的很喜欢我的单元测试。事情发生了,几年后,我发现自己正在处理具有许多质量问题的大型遗留代码库。
我试图找到一种向应用程序添加单元测试的方法,但最后却陷入了catch 22:
- 为了编写含义完整的单元测试,将需要重构代码。
- 没有单元测试,重构代码将太危险。
如果我们觉得自己像个英雄,并且在单元测试中喝了些辅助工具,那么我们仍然可以尝试一下,但是确实有风险,我们最终只能获得更多价值不大的测试代码,而现在还需要维护这些代码。
有时,最好以"设计"的方式来处理代码。
回答
拥有大量依赖关系的遗留代码绝对会让我们陷入困境。我们需要花很长时间才能解决所有问题。
从我们所说的看来,我们似乎试图依次保持每个模块的源代码完整,将其放置在模拟了外部依赖项的测试工具中。我在这里的建议是采取更加勇敢的步骤,尝试进行一些重构以消除(或者反转)依赖关系,这可能正是我们要避免的步骤。
我建议这样做是因为我猜这些依赖在我们编写测试时会杀死我们。如果可以消除依赖关系,从长远来看,我们一定会过得更好。
回答
我不知道这是否适用于项目,但
我们可以尝试从构建的链接阶段解决问题。
这将完全消除#include问题。
我们需要做的就是重新实现包含文件中的接口以执行所需的操作,然后仅链接到为在包含文件中实现接口而创建的模拟对象文件。
这种方法的最大缺点是构建系统更加复杂。
回答
响应中的压抑感是压倒性的...但是不要害怕,我们拥有一本神圣的书,可以驱除遗留C ++代码的魔鬼。如果我们排队使用遗留C ++代码超过一个星期,请认真购买该书。
转到第127页:可怕的包含依赖项的情况。 (现在我什至不在迈克尔·费瑟斯(Michael Feathers)的范围之内,但这里是我可以管理的简短答案。
问题:在C ++中,如果classA需要了解ClassB,则ClassB的声明是直接/文本形式包含在ClassA的源文件中。而且由于我们的程序员喜欢将其带到一个错误的极端,因此文件可以递归地包含成千上万个其他文件。构建需要数年..但是,至少它可以构建..我们可以等待。
现在说"很难在测试工具下实例化ClassA"是一种轻描淡写的说法。 (引用MF的示例Scheduler就是我们的海报问题孩子,拥有丰富的经验。)
#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):
#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个定义。因此,为调度程序测试创建一个单独的程序。
- 我们不会破坏程序中的任何依赖关系,因此不会使代码更简洁。
- 只要我们需要测试,就需要维护那些假货。
- 审美意识可能会冒犯一段时间。
将此技术用于具有严重依赖问题的非常庞大的类。不要经常使用或者轻描淡写。。以此为基础进行更深层次的重构。随着时间的推移,当我们提取更多的类时(通过他们自己的测试),可以将该测试程序带到谷仓后面。
有关更多信息,请阅读本书。无价。加油!