【问题标题】:Is it OK to copy & paste unit-tests when the logic is basically the same?当逻辑基本相同时可以复制和粘贴单元测试吗?
【发布时间】:2011-03-27 00:50:34
【问题描述】:

我目前有大约 10 个测试,用于测试我的俄罗斯方块棋子不会向左移动(如果路径中有棋子或墙壁)。现在,我必须测试正确的动作是否相同。

如果我只是复制我已经为左移进行的 10 个测试并只进行必要的更改并对代码本身也做同样的事情,这是否太糟糕了?还是我应该再去一次,从头开始做每个测试,即使逻辑基本相同?

【问题讨论】:

    标签: c# java unit-testing tdd


    【解决方案1】:

    尝试采用您未提及的第 3 种方法,即重构您的代码,以便您可以在所有 10 个测试之间共享一个测试实现。

    要点是,复制代码几乎总是错误的做法。在此示例中,您可以将检查代码重构为一个名为的方法,例如 IsTetrisPieceUnableToMoveLeftBecauseOfAPieceOrAWall。在为单元测试编写一些“共享”功能时,我总是会使用非常具有描述性的方法名称,因为它可以非常清楚地知道正在做什么/测试什么。

    【讨论】:

    • Is_Tetris_piece_unable_to_move_left_because_of_a_piece_or_a_wall
    • 感谢上帝自动完成。
    • @Inverse,当然,如果下划线为你做这件事 =)
    【解决方案2】:

    测试代码与任何其他代码一样,应该维护和重构。

    这意味着如果您有共享逻辑,请将其提取到自己的函数中。

    某些单元测试库(例如 xUnit 系列)具有用于此类共享代码的特定测试夹具、设置和拆卸属性。

    请参阅this 相关问题 - “为什么复制粘贴代码很危险?”。

    【讨论】:

      【解决方案3】:

      复制粘贴没有错,这是一个很好的起点。确实,这比从头开始要好,就好像你有工作代码(无论是测试还是其他),然后复制粘贴比从头开始更可靠,也更快。

      但是,这只是第 1 步。第 2 步是针对共性进行重构,第 1 步只是为了帮助您看到共性。如果你不用复制就可以看得很清楚(有时先复制再检查更容易,有时不是,这取决于做的人),然后跳过第 1 步。

      【讨论】:

        【解决方案4】:

        如果你在重复代码,那么你必须重构。您的情况是一个常见问题,可以使用“参数测试”解决。测试工具支持的参数测试允许将多组输入值作为参数传递。您可能还想查看 Fuzz testing,我发现它在这种情况下很有用。

        【讨论】:

        • 在给出的示例中,模糊测试有什么用处?
        【解决方案5】:

        我对此有一些争议。虽然在生产代码中必须尽可能避免代码重复,但这对于测试代码来说并不是那么糟糕。生产和测试代码性质和意图不同:

        • 生产代码可以承受一定的复杂性,以便于理解/维护。您希望代码处于正确的抽象级别,并且设计保持一致。这没关系,因为您对其进行了测试,并且可以确保它有效。如果您在逻辑级别上确实有 100% 的代码覆盖率,那么生产代码中的代码重复就不会成为问题。这真的很难实现,所以规则是:避免重复并最大化代码覆盖率。

        • 测试代码另一方面必须尽可能简单。您必须确保测试代码实际测试了它应该测试的内容。如果测试很复杂,您最终可能会在测试中遇到错误或错误的测试——而且您没有测试的测试,所以规则是:保持简单。如果测试代码是重复的,那么当它发生变化时这不是一个大问题。如果仅在一个测试中应用更改,则在您修复它之前,另一个将失败。

        我想说的主要一点是,生产代码和测试代码具有不同的性质。那么它总是一个常识问题,我并不是说你不应该考虑测试代码等。如果你可以在测试代码中考虑一些东西并且你确定它没问题,那就去做吧。但是对于测试代码,我更喜欢简洁而不是优雅,而对于生产代码,我更喜欢优雅而不是简洁。最佳当然是有一个简单、优雅的解决方案:)

        PS:如果你真的不同意,请发表评论。

        【讨论】:

        • +1,我觉得你的论点很有说服力。也许我们可以说如果你的代码有 x 级复杂度,那么它需要被测试。这也适用于测试代码。因此,如果适当的重构将测试代码复杂度提高到 x 以上,那么(1)重构并添加另一个测试级别;或 (2) 保持简单愚蠢。
        • 我同意你的观点,即测试代码应该很简单。但是,同时它应该是可维护的,并且复制代码不是你想要做的
        • @P.K 实际上,重复的代码在大多数情况下更容易理解,因为它的复杂性很低(抽象程度较低)。所以维护并不难,但可能有点重复。在软件工程中,大部分时间都花在了解需要做什么,而不是做它。如果维护测试是重复和无聊的,那很好,它应该是这样的。重复的问题是担心不更新所有地方的逻辑。您不能冒险让生产代码忽略不连贯,但在测试代码中,测试将失败,直到您更改它。
        • 保持测试代码尽可能简单。新手应该能看懂。避免抽象,因为它增加了不明显失败的风险。使用 xUnit 模式 IUseFixture 或 nUnit 共享设置和删除可以避免大量复制和粘贴以及样板测试代码,而不会造成混淆。
        • 测试代码和生产代码可能有不同的性质,我同意测试代码应该尽量保持它的方法简单,但我不同意代码重复应该被提倡。应尽可能避免。如果您的测试项目有数百个测试用例(集成级别),那么维护这些用例所需的工作很快就会变得笨拙。在由于必要的测试基础设施维护和开发变更涌入导致案例失败的情况下,让您的产品面临“问题蔓延”并不是一个理想的情况。
        【解决方案6】:

        请记住,您的测试正在推动您的代码。如果你发现你的测试看起来是重复的,除了左/右之类的东西,那么可能有一些底层代码左右重复。因此,您可能想看看是否可以重构代码以使用左或右并向其发送左或右标志。

        【讨论】:

        • 我不确定,正如我所看到的,测试代码的结构并不依赖于被测代码的结构。例如,我可以使用相同的测试代码来测试许多不同的实现HTTP 服务器。
        • 在测试中删除重复的想法是它可以帮助您在 SUT 上创建更抽象的接口。这将帮助您优化您正在测试的对象。另外请记住,测试中的重复次数越多,维护负担或技术债务就越高。
        【解决方案7】:

        xunitpatterns.org 网站说“不”(复制/粘贴不行),因为在需要更新测试时会增加成本:

        “剪切和粘贴”是快速编写代码的强大工具,但它 导致相同代码的许多副本,每个副本都必须是 并行维护。

        为了进一步阅读,它还链接到文章

        作者:Arie van Deursen、Leon Moonen、Alex van den Bergh、Gerard Kok

        【讨论】:

          【解决方案8】:

          我同意@Rob。代码需要重构。但是,如果您此时不想重构代码,那么您可以进行参数化测试。不同参数的相同测试运行。请参阅 nunit 中的 TestCase 和 TestCaseSource 属性。

          参考 http://nunit.org/index.php?p=parameterizedTests&r=2.5

          【讨论】:

            【解决方案9】:

            我有时会发现自己进行非常复杂的单元测试只是为了避免测试代码重复。我认为这样做不好。任何单个单元测试都应该尽可能简单。如果你需要复制来实现它 - 让它成为。

            另一方面,如果您的单元测试有 +100500 行代码,那么显然应该对其进行重构,这将是一种简化。

            当然,还要尽量避免无意义的单元测试重复,例如测试 1+1=2、2+2=4、3+3=6。如果您确实需要在不同的数据上测试相同的方法,请编写数据驱动的测试。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-30
              • 2017-09-28
              • 2013-03-17
              相关资源
              最近更新 更多