用C ++进行单元测试

时间:2009-06-29 20:59:35

标签: c++ unit-testing refactoring

是否可以使用接口来破坏所有依赖项,以使类可测试?由于许多虚拟调用而不是普通的方法调用,它在运行时会产生很大的开销。

测试驱动开发如何在现实世界的C ++应用程序中运行?我阅读了有效使用遗留代码,并且非常有用,但却没有加快练习TDD的速度。

如果我进行重构,它经常发生,因为大量的逻辑变化,我必须完全重写单元测试。我的代码更改经常改变数据处理的基本逻辑。我没有看到编写单元测试的方法,这些测试不需要在大型重构中进行更改。

可能有人可以指向我使用TDD进行学习的开源c ++应用程序。

7 个答案:

答案 0 :(得分:5)

更新:请参阅this question too.

我只能在这里回答一些部分:

可以使用接口来破坏所有依赖项,只是为了使类可测试吗?由于许多虚拟调用而不是普通的方法调用,它在运行时会产生很大的开销。

如果你的表现因此受到太大影响,那就没有(基准!)。如果您的开发受到太多影响,请不要(估计额外的努力)。如果从长远来看它似乎无关紧要并帮助你提高质量,是的。

您可以随时“熟悉”您的测试类,或者测试可以通过其调查其中的内容的TestAccessor对象。这避免了为了测试而使一切动态可调度。 (听起来确实有点工作。)

设计可测试的界面并不容易。有时你必须添加一些额外的方法来访问内部只是为了测试。它让你有点畏缩,但这样做很好,而且这些功能在实际应用中也很常见,迟早会有用。

如果我进行重构,它经常发生,因为大量的逻辑变化,我必须完全重写单元测试。我的代码更改经常改变数据处理的基本逻辑。我没有看到编写单元测试的方法,这些测试不需要在大型重构中进行更改。

通过定义的大型重构改变了很多,包括测试。很高兴你有他们,因为他们将在重构后测试东西。

如果你花费更多时间进行重构而不是制作新功能,或许你应该考虑在编码之前多想一些,以找到能够承受更多变化的更好的接口。此外,无论你做什么,在界面稳定之前编写单元测试都是一件痛苦的事。

对于变化很大的界面,您拥有的代码越多,每次更改的代码就越多。我认为你的问题就在那里。我已经设法在大多数地方拥有足够稳定的接口,并且偶尔只重构部分。

希望它有所帮助。

答案 1 :(得分:3)

我经常使用宏,#if和其他预处理器技巧来“模拟”依赖关系,以便在C和C ++中进行单元测试,正是因为有了这样的宏,我不需要支付任何费用。代码编译为生产而不是测试时的运行时成本。不优雅,但相当有效。

至于重构,他们可能需要更改测试,因为它们如你所描述的那样过于庞大和具有侵略性。不过,我并没有发现自己如此彻底地进行重构。

答案 2 :(得分:1)

显而易见的答案是使用模板而不是接口来分解依赖关系。当然,这可能会损害编译时间(具体取决于您实现它的方式),但至少应该消除运行时开销。一个稍微简单的解决方案可能就是依赖一组typedef,这些typedef可以与一些宏或类似的东西交换出来。

答案 3 :(得分:1)

关于你的第一个问题 - 为了测试而破解事物是很有必要的,尽管有时候你可能需要先破坏它们,然后再将它们作为重构的一部分。软件产品最重要的标准是它可以工作,而不是它是可测试的。可测试只是重要的,因为它可以帮助您使产品更稳定,并为您的最终用户提供更好的功能。

测试驱动开发的一个重要部分是选择代码的小型原子部分,这些部分不太可能因单元测试而发生变化。如果由于大量逻辑更改而不得不重写大量单元测试,则可能需要在更细粒度级别进行测试,或者重新设计代码以使其更稳定。稳定的设计不应随着时间的推移而发生剧烈变化,测试不会帮助您避免大规模重构。但是,如果完成正确的测试可以使得当你重构事物时,你可以更加确信你的重构是成功的,假设有一些测试不需要改变。

答案 4 :(得分:1)

可以使用接口来破坏所有依赖项,只是为了使类可测试吗?由于许多虚拟调用而不是普通的方法调用,它在运行时会产生很大的开销。

我认为打破依赖关系是可以的,因为这会带来更好的接口。

如果我进行重构,它经常发生,因为大量的逻辑变化,我必须完全重写单元测试。我的代码更改经常改变数据处理的基本逻辑。我没有看到编写单元测试的方法,这些测试不需要在大型重构中进行更改。

您不会以任何语言获取这些大型重构,因为您的测试应该表达您的代码的真实意图。因此,如果逻辑发生变化,您的测试必须改变。

也许你并没有真正做TDD,比如:

  1. 创建失败的测试
  2. 创建传递测试的代码
  3. 创建另一个失败的测试
  4. 修复代码以通过两个测试
  5. 冲洗并重复,直到您认为有足够的测试来显示您的代码应该做什么
  6. 这些步骤表明你应该做一些小改动,而不是大改变。如果你留在后者,你无法逃脱大型重构。没有任何语言可以帮助你,因为编译时间,链接时间,错误消息等等,C ++将是最糟糕的。

    我实际上是在用C ++编写的真实世界软件中工作,其下有一个巨大的遗留代码。我们正在使用TDD,它确实有助于改进软件的设计。

答案 5 :(得分:0)

  

如果我进行重构,它经常发生,因为大量的逻辑变化,我必须完全重写单元测试。 ...我没有看到编写单元测试的方法,这些测试不需要在大型重构中进行更改。

There are multiple layers of testing,其中一些图层在重大逻辑更改后也不会中断。另一方面,单元测试旨在测试方法和对象的内部,并且需要更频繁地进行更改。一定没有错。事情就是这样。

  

是否可以使用接口来破坏所有依赖项以使类可测试?

It's definitely OK to design classes to be more testable。毕竟,这是TDD目的的一部分。

  

由于许多虚拟调用而不是普通的方法调用,它在运行时会产生大量开销。

几乎每家公司都有一些所有员工都应遵循的规则清单。无能为力的公司只列出他们能想到的每一个优质产品(“我们的员工高效,负责,道德,永不偷工减料”)。更聪明的公司实际上排名优先考虑。如果有人想出一种不道德的方式来提高效率,公司会这样做吗?最好的公司不仅打印出小册子,说明优先级如何排名,而且还确保管理层遵循排名。

程序完全可以高效且易于测试。但有时您需要选择哪个更重要。这是其中一次。我不知道效率对你和你的计划有多重要,但是你做到了。所以“你宁愿做一个缓慢的,经过良好测试的程序,还是没有全面测试覆盖的快速程序?”

答案 6 :(得分:0)

  

它涉及很大的开销   运行时由于许多虚拟调用   而不是普通的方法调用。

请记住,如果通过指针(或ref。)访问接口或对象的方法,它只是一个虚拟的调用开销。如果通过堆栈中的具体对象访问该方法,它将不具有虚拟开销,甚至可以内联。

此外,在分析代码之前,永远不要假设这种开销很大。几乎总是,如果与您的方法正在进行比较,虚拟调用就毫无价值。 (大多数惩罚来自无法内联单行方法,而不是来自额外的呼叫间接)。