2009-06-29 88 views
5

可以使用接口打破所有依赖关系,只是为了使类可测试?由于许多虚拟调用而不是普通方法调用,因此它在运行时会涉及大量开销。在C++中进行单元测试

测试驱动开发如何在现实世界的C++应用程序中工作?我阅读了使用遗留代码有效地工作,并且喜欢它非常有用,但是不赶快加速实践TDD。

如果我这样做,很多时候,我不得不因为大量的逻辑完全改变rewite单元测试中发生重构。我的代码更改经常会改变数据处理的基本逻辑。我没有看到编写单元测试的方式,在大型重构中不必改变。

可能有人可以点我一个开源C++应用程序至极使用TDD通过例子来学习。

回答

5

更新:见this question too.

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

是好的,打破使用接口的所有相关性只是为了一类可测试?由于许多虚拟调用而不是普通方法调用,因此它在运行时会涉及大量开销。

如果你的表现会受太大的缘故吧,没有(基准!)。如果你的发展受到太多影响,否(估计额外的努力)。如果它看起来不会有太大影响,并且从长远来看有所帮助,并帮助您提高质量,是的。

你总是可以'测试'你的测试类,或者一个TestAccessor对象,通过它你的测试可以调查其中的东西。这避免了为了测试而动态分配所有内容。 (它的确听起来很不错)

设计可测试的接口并不容易。有时你必须添加一些额外的方法来访问内部进行测试。它会让你感到畏缩,但它有好处,而且往往不是那些函数在实际应用中有用,迟早也会如此。

如果我做了重构,它经常发生,因为大规模的逻辑改变,我必须完全重新进行单元测试。我的代码更改经常会改变数据处理的基本逻辑。我没有看到编写单元测试的方式,在大型重构中不必改变。

defintion的大型重构变化很大,包括测试。很高兴你有他们,因为他们也会在重构之后测试一些东西。

如果您花费更多的时间重构比创建新功能,也许您应该考虑在编码之前多思考一下,以便找到能够承受更多变化的更好的界面。另外,在接口稳定之前编写单元测试是一件痛苦的事情,不管你做什么。

对接口变化很大的代码越多,每次更改的代码越多。我认为你的问题在于此。我已经在大部分地方设置了足够稳定的接口,并且不时重构部分。

希望它有帮助。

+0

这不是我编码之前不认为的。在客户看到新功能的初始实现后,需求常常会发生变化。另一个原因是,出于市场营销的原因,做一个快速和肮脏的实施有时是不切实际的。在这种情况下,我不打算进行单元测试,但有时由于紧张的调节,有时候快速和肮脏的黑客变成了真实的东西。以后再做这个烂摊子测试并不容易。 – frast 2009-06-29 22:18:00

3

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

至于重构,他们很可能需要的时候,他们是如此压倒性地大,intrustive你描述更改测试。不过,我并没有发现自己经常那么剧烈地重构。

+1

我想到你的解决方案,但我担心它可能会发生,因为预处理器定义,我测试的不是“真正的代码”。但我认为这是值得尝试的。 – frast 2009-06-29 22:07:55

+0

是的,宏和其他预处理器攻击是脆弱的东西,除非小心谨慎地使用,并根据特定的有限模式 - 这是我的答案中的“不雅”部分。依赖注入的宏观等价物(有时也可以通过模板或typedef实现,就像@jaif所说的,在简单的情况下)是一个非常有限和严格的案例,而这就是所有你需要让你“模拟”你的依赖为单元测试目的。 – 2009-06-29 22:31:39

1

明显的答案是使用模板而不是接口来分解依赖关系。当然这可能会影响编译时间(具体取决于您如何实现它),但至少应该消除运行时间开销。稍微简单一些的解决方案可能仅仅依赖一组typedef,这些typedef可以用一些宏或类似的东西来替换掉。

1

关于你的第一个问题 - 这是很少值得打破东西只是为了测试的缘故,虽然有时你可能使他们更好地为重构的一部分,之前打破东西。软件产品最重要的标准是它的工作原理,而不是可测试的。可测试性是非常重要的,因为它可以帮助您制作更稳定,更适合最终用户的产品。

测试驱动开发的一个重要组成部分就是选择你的代码是不太可能改变单元测试小的原子零件。如果由于大规模逻辑改变而必须重写很多单元测试,则可能需要在更细粒度的级别进行测试,或者重新设计代码以使其更稳定。一个稳定的设计不应该随着时间的推移发生巨大的变化,测试也不会帮助您避免大规模重构。然而,如果正确的测试可以使得当你重构一些东西时,你可以更加确信你的重构是成功的,假设有一些测试不需要改变。

1

可以打破所有使用接口的依赖只是为了让一个类可测试吗?由于许多虚拟调用而不是普通方法调用,因此它在运行时会涉及大量开销。

我认为可以打破依赖关系,因为这会导致更好的界面。

如果我做了重构,它经常发生,因为大规模的逻辑改变,我必须完全重新进行单元测试。我的代码更改经常会改变数据处理的基本逻辑。我没有看到编写单元测试的方式,在大型重构中不必改变。

由于您的测试应该表达您的代码的真实意图,所以您不会在任何语言中使用这些大型重构。所以如果逻辑改变了,你的测试必须改变。

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

  1. 创建一个测试失败
  2. 创建代码通过测试
  3. 创建另一个测试失败
  4. 修复代码通过两个测试
  5. 冲洗并重复,直到您认为您有足够的测试,显示您的代码应该做什么

这些步骤说你应该做一些小改动,而不是大改动。如果你留在后者,你无法逃避重构。没有一种语言能够帮你节省开支,而且由于编译时间,链接时间,错误消息等等,C++将是最糟糕的。

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

0

如果我做了重构,它经常发生,因为大规模的逻辑改变,我必须完全重写单元测试。 ......我没有看到编写单元测试的方式,在大型重构中不必改变。

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

是否可以使用接口来打破所有依赖关系,只是为了使类可测试?

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

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

几乎每个公司都有一些所有员工应遵循的规则列表。无知的公司只是列出他们能想到的每一个好品质(“我们的员工是高效,负责任,有道德,而且从不偷工减料”)。更聪明的公司实际上将他们的优先级排序如果有人提出不道德的方式来提高效率,公司是否会这样做?最好的公司不仅打印宣传册,说明优先级如何排名,而且还确保管理层遵循排名。

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

0

它涉及到在 运行,因为很多虚拟呼叫 不是纯方法调用的显著开销。

请记住,如果您通过指向接口或对象的指针(或参考)访问方法,则它只是虚拟调用开销。如果通过堆栈中的具体对象访问该方法,则不会产生虚拟开销,甚至可以内联。

此外,在分析代码之前,千万不要认为这个开销很大。几乎总是,如果你比较你的方法正在做什么,虚拟调用是毫无价值的。 (大部分的惩罚来自于不可能内联单线方法,而不是来自额外的间接呼叫)。