【问题标题】:How to fake C++ classes containing non-virtual functions?如何伪造包含非虚函数的 C++ 类?
【发布时间】:2014-12-30 16:35:31
【问题描述】:

我正在尝试测试一些 C++ 遗留代码。特别是,我有一个类层次结构,比如A < B < C(即AB 的子类,BC 的子类),并且有一个对象的全局引用类型为C,用于整个系统代码(单例模式)。目标是用一些假对象替换 C 对象(实际上,C 用于访问数据库)。

我的第一个尝试是引入接口IA, IB, and IC(包含对应类功能的纯虚版本),让每个类实现自己的接口,并将全局C引用的类型改为IC .在我测试的设置函数中,我会用我自己的IC 实现替换全局引用的C 对象,让整个系统使用我的假实现。

但是,A, BC 类都包含相当多的非虚拟函数。现在,如果我要让类从我的接口继承,我会将这些函数的语义从非虚拟更改为虚拟(Feathers 在“使用遗留代码高效工作”中讨论了这个问题,第 367 页)。换句话说:我必须检查对我的全局对象的每一次调用,并且我必须确保在我的更改之后,仍然调用相同的函数。对我来说,这听起来像是很多容易出错的工作。

我也想过让非虚函数“final”,即告诉编译器A, BC的函数不能隐藏在子类中(这会让编译器告诉我所有潜在的危险BC 的函数 - 如果函数没有隐藏在基类中,则根本不会发生上述效果),但 C++ 似乎不支持(我们还没有使用 C+ +11,但即使它的 final 关键字似乎也只适用于虚函数)。

为了让情况更加困难,A, BC 类还包含公共属性、虚函数以及一些模板函数。

所以我的问题是:如何应对我上面描述的情况?是否有任何我错过的 C++ 功能,在我的场景中可以提供帮助?有什么设计模式吗?甚至任何重构工具?我的主要要求是更改必须尽可能安全,因为我想伪造的类对系统相当重要......我也很高兴有一个“丑陋”的解决方案,它可以让我进行测试到位(如果系统适当地覆盖了测试,则可以稍后对其进行重构)。

编辑:我搞砸了我的继承层次结构(它是颠倒的) - 现在已更正。

Edit2:我们最终得到如下结果:我们只将我们当前测试用例实际需要的函数设为虚拟。然后,我们检查了对这些方法的每次调用(这是可管理的)。这让我们可以使用 Google Mocks 来模拟我们的课程。有了越来越多的测试用例,希望我们的更改可以节省时间。请注意,在问我的问题时,我认为 Google Mocks 只能模拟纯接口; 不是这种情况,因此允许采用如上所述的增量方法。

【问题讨论】:

  • 通常情况下,测试的第一个问题是全局对象,所以我建议在做任何其他事情之前摆脱它。
  • A 和/或 B 是具体的类吗?如果是这样,他们需要吗?
  • @Puppy:我们的系统有很多全局对象,我认为这是一个典型的先有鸡还是先有蛋的问题:为了安全地摆脱全局变量,我需要测试,并编写测试,我需要摆脱全局变量。因此,我现在尝试通过最少且尽可能安全的重构来进行测试,并且我计划稍后解决完整的全局问题。
  • @Mark B:它们是具体的类。很难看出它们是否确实需要具体,但我的猜测是它们不需要。为什么要问?

标签: c++ unit-testing inheritance virtual-functions


【解决方案1】:

我将建议将C 更改为模板,其中模板类型是实际的数据库访问/实现代码。然后在您的直播节目中,您在当前使用C 的任何地方使用C<LiveDatabase>,在测试时使用C<MockDatabase>。然后AB 保持不变,C 的所有内部工作都保持不变,只有特定的数据库调用不同(在它们对委托实现的调用中)。

接下来我将着手处理公共属性,因为它们只会让您头疼和难以发现的错误。只需将它们全部设为私有,并让您的编译器告诉您它们是否正在被访问。对于只读使用,只需添加访问器就非常容易。在它们发生突变的地方,您需要确定类的适当接口以实现所需的突变。不要只添加一个 mutator 方法,除非那绝对是你最后的选择。

【讨论】:

  • 是否有理由更喜欢模板而不是简单地将数据库层提取到 C 实例化时提供的单独对象中?
  • @Dave Bacher 模板可以通过允许编译器在编译时知道类型来提供更好的性能。
  • “模板创意”是一个有趣的建议,我一定会在明年评估 - 感谢您的建议!
【解决方案2】:

编辑:根据 Dietmar Kühl 在下面的评论,这种方法无法伪造成员函数模板。

我不确定这是否可行,但请尝试在单独的 .cpp 文件中创建一个假类(非虚拟类)。然后,将测试与假课程联系起来,而不是真正的课程。理论上两者应该有相同的ABI(Application Binary Interface),所以两者应该是兼容的。

【讨论】:

  • 如果不是这个类中的成员函数模板,那可以工作。对于成员函数模板,几乎可以肯定,至少需要使用测试版本进行编译。
  • 啊,我错过了。是的,使用成员模板函数,这种方法无法伪造这些函数。
  • 如果你换掉标题和库,它可以工作。您可以定义一个宏,然后使包含条件以该宏为条件。
  • 这似乎也是一个有趣的建议——再次感谢!我稍后会用我的经验更新这个问题......
猜你喜欢
  • 1970-01-01
  • 2015-02-13
  • 1970-01-01
  • 2012-09-21
  • 1970-01-01
  • 1970-01-01
  • 2017-08-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多