【问题标题】:unit tests in C++C++ 中的单元测试
【发布时间】:2009-06-29 20:59:35
【问题描述】:

为了使类可测试而使用接口打破所有依赖关系是否可行?由于许多虚拟调用而不是普通的方法调用,它在运行时涉及大量开销。

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

如果我进行重构,由于大量的逻辑更改,我经常不得不完全重写单元测试。我的代码更改经常改变数据处理的基本逻辑。我看不出有一种方法可以编写不必在大型重构中更改的单元测试。

可能有人可以将我指向一个使用 TDD 通过示例学习的开源 C++ 应用程序。

【问题讨论】:

    标签: c++ unit-testing refactoring


    【解决方案1】:

    更新:见this question too.

    这里只能回答一部分:

    为了使类可测试而使用接口打破所有依赖关系是否可行?由于许多虚拟调用而不是普通的方法调用,它在运行时涉及大量开销。

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

    您始终可以“加好友”您的测试类,或者一个 TestAccessor 对象,您的测试可以通过该对象调查其中的内容。这样可以避免使所有内容都可以动态调度以进行测试。 (听起来确实需要做很多工作。)

    设计可测试的界面并不容易。有时您必须添加一些额外的方法来访问内部结构只是为了进行测试。它让你有点畏缩,但拥有它是件好事,而且这些功能迟早在实际应用程序中也很有用。

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

    按定义进行的大型重构会发生很大变化,包括测试。很高兴你拥有它们,因为它们也会在重构后测试东西。

    如果您花费更多时间进行重构而不是制作新功能,那么也许您应该考虑在编码之前多考虑一下,以找到可以承受更多更改的更好接口。此外,无论您做什么,在接口稳定之前编写单元测试都是一种痛苦。

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

    希望对你有帮助。

    【讨论】:

    • 并不是我在编码之前就没有想到。在客户看到新功能的初始实现后,需求经常发生变化。另一个原因是,出于营销原因,有时需要进行快速而肮脏的实施。在这种情况下,我不会为单元测试而烦恼,但有时由于时间紧迫,快速而肮脏的 hack 会变​​成真实的东西。以后要让这个烂摊子可测试并不容易。
    • 对不起,我不是故意的。 ;) 我知道现实生活有时很难相处。 Quick'n 肮脏的黑客攻击伴随着债务,要么缺少测试,要么进行必要的重构/清理,要么通常两者兼而有之。您要么先支付现金(适当的工作),要么先支付租金(清理)。没有绕过那个。在你无法影响它的条件下工作是很困难的。 (去过那里...)
    【解决方案2】:

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

    至于重构,当测试如您所描述的那样庞大且令人信服时,它们很可能需要更改测试。不过,我发现自己并不经常如此彻底地重构。

    【讨论】:

    • 我在考虑你的解决方案,但我担心由于预处理器的定义,我正在测试的不是“真实代码”。但我觉得值得一试。
    • 是的,宏和其他预处理器黑客是脆弱的东西,除非小心使用并根据特定的有限模式 - 这是我回答中“不优雅”的部分。依赖注入的宏观等价物(有时也可以通过模板或 typedefs 实现,正如@jaif 所说,在简单的情况下)是一个非常有限和有纪律的情况,不过,这就是让你“模拟”你的依赖项所需要的一切用于单元测试目的。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      关于您的第一个问题 - 仅仅为了测试而破坏事物很少值得,尽管有时您可能必须先破坏事物,然后才能将它们作为重构的一部分进行改进。软件产品最重要的标准是它可以工作,而不是它的可测试性。可测试性很重要,因为它可以帮助您为最终用户制作更稳定且功能更好的产品。

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

      【讨论】:

        【解决方案5】:

        为了使类可测试而使用接口打破所有依赖关系是否可行?由于许多虚拟调用而不是普通的方法调用,它在运行时涉及大量开销。

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

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

        您不会使用任何语言进行这些大型重构,因为您的测试应该表达代码的真正意图。因此,如果逻辑发生变化,您的测试也必须发生变化。

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

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

        这些步骤表明您应该进行小改动,而不是大改动。如果你坚持后者,你就无法逃脱大的重构。没有任何语言可以拯救你,而 C++ 将是其中最糟糕的,因为编译时间、链接时间、错误消息等等等等。

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

        【讨论】:

          【解决方案6】:

          如果我进行重构,我经常会因为大量的逻辑更改而不得不完全重写单元测试。 ...我看不到编写不必在大型重构中更改的单元测试的方法。

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

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

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

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

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

          程序完全有可能高效且易于测试。但是,有时您需要选择哪个更重要。这是其中之一。我不知道效率对你和你的程序有多重要,但你知道。那么“您更愿意拥有一个缓慢的、经过良好测试的程序,还是一个没有全面测试覆盖率的快速程序?”

          【讨论】:

          • 你确实提出了很好的观点。对于您的最后一个问题,我认为我们应该根据案例考虑性能与测试覆盖率。并非我们应用程序的所有部分都需要最高性能。
          【解决方案7】:

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多