我正在尝试将一些C ++遗留代码置于测试之中。特别是,我有一个类层次结构,比如A < B < C
(即A
是B
的子类,B
是C
的子类) ,并且存在对C
类型的对象的全局引用,该对象从整个系统的代码(单例模式)中使用。目标是用一些假对象替换C
对象(实际上,C
用于访问数据库)。
我的第一次尝试是引入接口IA, IB, and IC
(其中包含相应类的函数的纯虚拟版本),让每个类实现其接口,并更改全局C
引用的类型到IC
。在我的测试的setup函数中,我将用我自己的C
实现替换全局引用的IC
对象,使整个系统使用我的假实现。
但是,类A, B
和C
每个都包含很多非虚函数。现在,如果我要从接口继承类,我会将这些函数的语义从非虚拟更改为虚拟(Feathers在“使用遗留代码高效工作”中讨论了这个问题,第367页)。换句话说:我必须检查对我的全局对象的每次调用,并且我必须确保在我的更改之后仍然调用相同的函数。这对我来说听起来像很多错误。
我还考虑过让非虚函数“最终”,即告诉编译器A, B
和C
的函数不能隐藏在子类中(这会使编译器告诉我B
和C
的所有潜在危险函数 - 如果函数未隐藏在基类中,上述效果根本不会发生),但C ++似乎不支持(我们还没有使用C ++ 11,但即使它的最终关键字似乎也只适用于虚函数)。
为了使情况更加困难,类A, B
和C
也包含公共属性,虚函数以及一些模板函数。
所以我的问题是:如何应对我上面描述的情况?有没有我错过的C ++功能,哪些可以帮助我的场景?任何设计模式?甚至是任何重构工具?我的主要要求是变化必须尽可能安全,因为我想要伪造的类对系统来说非常重要......我也会对一个“丑陋”的解决方案感到满意,这个解决方案可以让我进行测试到位(如果系统适当地覆盖了测试,可以在以后重构)。
编辑:我搞砸了我的继承层次结构(它是颠倒的) - 现在已经纠正了。
Edit2:我们最终结果如下:我们只制作了我们当前测试用例实际需要的虚拟函数。然后我们检查了对这些方法的每次调用(这是可管理的)。这使我们可以使用Google Mocks模拟我们的课程。有越来越多的测试用例,希望我们的变化能够随着时间的推移而变得更加节省。请注意,在提出我的问题时,我认为Google Mocks只能模拟纯接口;这是 not 的情况,因此允许如上所述的增量方法。
答案 0 :(得分:2)
我建议将C
更改为模板,其中模板类型是实际的数据库访问/实现代码。然后,在您的实时计划中,您使用C<LiveDatabase>
当前使用C
的任何位置,并在测试时使用C<MockDatabase>
。然后A
和B
保持不变,C
内部工作的所有部分保持不变,只有特定的数据库调用不同(在他们对委托实现的调用中)。 / p>
接下来我会继续研究公共属性,因为它们只会让你头痛并且难以发现错误。只需简单地将它们全部设为私有,让你的编译器告诉你他们是否被访问了。对于只读使用,只需添加访问器就非常容易。在他们被突变的地方,您需要确定适合您的类的接口以实现所需的突变。不要只添加一个mutator方法,除非这绝对是你的最后一个选择。
答案 1 :(得分:1)
编辑:根据DietmarKühl的评论,这种方法无法伪造成员函数模板。
我不确定这是否可行,但尝试在单独的.cpp文件中创建一个假类(非虚拟)。然后,将测试与假类连接,但不是真实类。从理论上讲,这两个具有相同的ABI(应用二进制接口),因此两者应该是兼容的。