单元测试C ++:要测试什么?


20

TL; DR

编写好的,有用的测试很困难,并且在C ++中代价很高。您是否有经验的开发人员可以就什么以及何时进行测试分享您的理论依据?

很长的故事

我曾经做过测试驱动的开发,实际上是整个团队,但对我们来说效果不佳。我们有很多测试,但是它们似乎从来没有覆盖我们有实际错误和回归的情况-通常是在单元交互时发生的,而不是因为它们孤立的行为而发生。

这通常很难在单元级别进行测试,以至于我们停止了TDD测试(组件确实可以加快开发速度),而花了更多时间增加集成测试的覆盖范围。尽管小型单元测试从未捕获任何实际的错误,并且基本上只是维护开销,但集成测试确实值得付出努力。

现在,我继承了一个新项目,并且想知道如何进行测试。它是本机C ++ / OpenGL应用程序,因此集成测试并不是真正的选择。但是C ++中的单元测试比Java中的要难一些(您必须显式地制作东西virtual),并且该程序不是很面向对象的,所以我无法模拟/存根一些东西。

我不想为了编写测试而仅仅为了编写一些测试而拆开并OO化整个过程。所以我问你:我应该为测试写什么?例如:

  • 我希望经常更改的函数/类?
  • 难以手动测试的函数/类?
  • 已经易于测试的功能/类?

我开始研究一些受人尊敬的C ++代码库,以了解它们如何进行测试。现在,我正在研究Chromium源代码,但发现很难从代码中提取其测试依据。如果有人有一个很好的例子或关于C ++用户(委员会,书籍作者,Google,Facebook,Microsoft等人的看法)如何发表文章的帖子,那将特别有帮助。

更新资料

自编写此书以来,我一直在这个网站和网络上进行搜索。找到了一些好东西:

可悲的是,所有这些都是以Java / C#为中心的。用Java / C#编写大量测试不是一个大问题,因此收益通常超过了成本。

但是正如我上面所写,在C ++中要困难得多。尤其是如果您的代码库不是那么OO,那么您就必须认真弄乱事情,以获得良好的单元测试覆盖率。例如:我继承的应用程序具有一个Graphics名称空间,该名称空间是OpenGL之上的薄层。为了测试任何实体(它们都直接使用其功能),我必须将其变成一个接口和一个类,并将其注入所有实体中。那只是一个例子。

因此,在回答这个问题时,请记住,我必须在编写测试方面投入大量资金。


3
对于单元测试C ++的难度,+ 1。如果您的单元测试要求您更改代码,请不要。
DPD

2
@DPD:我不太确定,如果真的值得测试怎么办?在当前的代码库中,我几乎无法测试仿真代码中的任何内容,因为它们都直接调用了图形函数,并且我无法模拟/存根它们。我现在可以测试的只是实用程序功能。但是我同意,更改代码以使其“可测试”感觉……是错误的。TDD的支持者经常说,这将以所有可以想象的方式使您的所有代码变得更好,但是我谦卑地不同意。并非所有内容都需要接口和几种实现。
futlib

让我给你举一个最近的例子:我花了整整一天的时间来测试一个单一的函数(用C ++ / CLI编写),测试工具MS Test总是会崩溃。普通CPP参考似乎有一些问题。相反,我只是测试了其调用函数的输出,并且效果很好。我浪费了一整天的时间来使用UT一项功能。那是浪费宝贵的时间。另外,我没有任何适合我需要的拔桩工具。我尽可能进行了手动存根。
DPD 2012年

我只是想避免这种事情:DI,我们的C ++开发人员必须特别注重测试。您确实完成了测试,所以我想没关系。
futlib

@DPD:我已经对此进行了更多思考,我认为您是对的,问题是我想进行哪种权衡。是否值得重构整个图形系统以测试几个实体?我知道那里没有任何错误,所以大概是:不。如果它开始出现错误,我将编写测试。太糟糕了,我不能接受您的回答,因为这是评论:)
futlib

Answers:


5

好吧,单元测试只是其中的一部分。集成测试可以帮助您解决团队中的问题。集成测试可以针对各种应用程序编写,也可以针对本机和OpenGL应用程序编写。您应该查看Steve Freemann和Nat Pryce撰写的“由测试指导的面向对象的增长软件”(例如,http: //www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 )。它引导您逐步完成具有GUI和网络通信的应用程序的开发。

并非由测试驱动的测试软件是另一个故事。检查Michael Feathers“有效地使用旧版代码”(http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052)。


我两本书都知道。事情是:1.我们不想使用TDD,因为它与我们的配合不好。我们确实需要测试,但不是虔诚的。2.我确信可以通过某种方式对OpenGL应用程序进行集成测试,但是这会花费很多精力。我想继续改进应用程序,而不是启动研究项目。
futlib

请注意,“事后”的单元测试总是比“先测试”更难,因为代码的设计目的不是可测试的(可重用,可维护等)。如果您仍然想这样做,即使您避免使用TDD,也要坚持使用Michael Feathers的技巧(例如接缝)。例如,如果要扩展功能,请尝试使用“发芽方法”之类的方法,并尝试使新方法可测试。可以做,但是恕我直言。
EricSchaefer

我同意非TDD代码的设计目的不是可测试的,但我不会说它本身不可维护或可重用-正如我在上面的评论中所述,有些事情不需要接口和多个实现。Mockito根本不是问题,但是在C ++中,我需要对我想存根/模拟的所有函数进行虚拟化。无论如何,不​​可测试的代码是我目前最大的问题:我必须更改一些非常基础的内容才能使某些部分可测试,因此我想对测试内容有一个合理的了解,以确保它值得。
futlib

您当然是对的,我会小心谨慎地使我编写的任何新代码都可测试。但这并不是一件容易的事,因为现在该代码库中的工作方式已经有了。
futlib

添加功能部件时,请考虑如何进行测试。您能注入任何难看的依赖吗?您怎么知道该函数完成了它应该做的事情。你能观察到任何行为吗?您可以检查任何结果的正确性吗?您可以检查任何不变式吗?
EricSchaefer

2

TDD太可惜了“对您来说效果不好”。我认为这是了解转机的关键。重温并了解TDD如何工作,您能做得更好,为什么会有困难。

因此,当然,您的单元测试没有发现您发现的错误。这就是重点。:-)您没有找到这些错误,因为您首先考虑了接口应该如何工作以及如何确保对其进行了正确的测试,从而避免了它们的发生。

正如您所得出的结论,要回答这个问题,就很难设计出未经测试的单元测试代码。对于现有代码,使用功能或集成测试环境比单元测试环境更为有效。针对特定区域测试系统整体。

当然,新的发展将受益于TDD。随着新功能的添加,针对TDD的重构可能有助于测试新开发,同时还允许针对遗留功能开发新的单元测试。


4
我们对TDD进行了大约一年半的时间,所有人都对此充满热情。但是,将TDD项目与没有TDD(但没有测试)的早期项目进行比较,我不会说它们实际上更稳定,或者具有更好的设计代码。也许是我们的团队:我们进行了很多配对和审查,我们的代码质量一直都很好。
futlib

1
我想得越多,我就越觉得TDD不能很好地适应该特定项目的技术:Flex / Swiz。发生了许多事件,绑定和注入,这使得对象之间的交互变得复杂,几乎无法进行单元测试。将这些对象去耦并不能使其更好,因为它们首先可以正确地工作。
futlib

2

我尚未在C ++中完成TDD,所以我无法对此发表评论,但是您应该测试代码的预期行为。尽管实现可以更改,但是行为(通常是?)应该保持不变。在以Java \ C#为中心的世界中,这意味着您仅测试公共方法,针对预期的行为编写测试,然后在实现之前进行测试(通常比做起来更好:)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.