【发布时间】:2011-03-27 00:50:34
【问题描述】:
我目前有大约 10 个测试,用于测试我的俄罗斯方块棋子不会向左移动(如果路径中有棋子或墙壁)。现在,我必须测试正确的动作是否相同。
如果我只是复制我已经为左移进行的 10 个测试并只进行必要的更改并对代码本身也做同样的事情,这是否太糟糕了?还是我应该再去一次,从头开始做每个测试,即使逻辑基本相同?
【问题讨论】:
标签: c# java unit-testing tdd
我目前有大约 10 个测试,用于测试我的俄罗斯方块棋子不会向左移动(如果路径中有棋子或墙壁)。现在,我必须测试正确的动作是否相同。
如果我只是复制我已经为左移进行的 10 个测试并只进行必要的更改并对代码本身也做同样的事情,这是否太糟糕了?还是我应该再去一次,从头开始做每个测试,即使逻辑基本相同?
【问题讨论】:
标签: c# java unit-testing tdd
尝试采用您未提及的第 3 种方法,即重构您的代码,以便您可以在所有 10 个测试之间共享一个测试实现。
要点是,复制代码几乎总是错误的做法。在此示例中,您可以将检查代码重构为一个名为的方法,例如 IsTetrisPieceUnableToMoveLeftBecauseOfAPieceOrAWall。在为单元测试编写一些“共享”功能时,我总是会使用非常具有描述性的方法名称,因为它可以非常清楚地知道正在做什么/测试什么。
【讨论】:
Is_Tetris_piece_unable_to_move_left_because_of_a_piece_or_a_wall
测试代码与任何其他代码一样,应该维护和重构。
这意味着如果您有共享逻辑,请将其提取到自己的函数中。
某些单元测试库(例如 xUnit 系列)具有用于此类共享代码的特定测试夹具、设置和拆卸属性。
请参阅this 相关问题 - “为什么复制粘贴代码很危险?”。
【讨论】:
复制粘贴没有错,这是一个很好的起点。确实,这比从头开始要好,就好像你有工作代码(无论是测试还是其他),然后复制粘贴比从头开始更可靠,也更快。
但是,这只是第 1 步。第 2 步是针对共性进行重构,第 1 步只是为了帮助您看到共性。如果你不用复制就可以看得很清楚(有时先复制再检查更容易,有时不是,这取决于做的人),然后跳过第 1 步。
【讨论】:
如果你在重复代码,那么你必须重构。您的情况是一个常见问题,可以使用“参数测试”解决。测试工具支持的参数测试允许将多组输入值作为参数传递。您可能还想查看 Fuzz testing,我发现它在这种情况下很有用。
【讨论】:
我对此有一些争议。虽然在生产代码中必须尽可能避免代码重复,但这对于测试代码来说并不是那么糟糕。生产和测试代码性质和意图不同:
生产代码可以承受一定的复杂性,以便于理解/维护。您希望代码处于正确的抽象级别,并且设计保持一致。这没关系,因为您对其进行了测试,并且可以确保它有效。如果您在逻辑级别上确实有 100% 的代码覆盖率,那么生产代码中的代码重复就不会成为问题。这真的很难实现,所以规则是:避免重复并最大化代码覆盖率。
测试代码另一方面必须尽可能简单。您必须确保测试代码实际测试了它应该测试的内容。如果测试很复杂,您最终可能会在测试中遇到错误或错误的测试——而且您没有测试的测试,所以规则是:保持简单。如果测试代码是重复的,那么当它发生变化时这不是一个大问题。如果仅在一个测试中应用更改,则在您修复它之前,另一个将失败。
我想说的主要一点是,生产代码和测试代码具有不同的性质。那么它总是一个常识问题,我并不是说你不应该考虑测试代码等。如果你可以在测试代码中考虑一些东西并且你确定它没问题,那就去做吧。但是对于测试代码,我更喜欢简洁而不是优雅,而对于生产代码,我更喜欢优雅而不是简洁。最佳当然是有一个简单、优雅的解决方案:)
PS:如果你真的不同意,请发表评论。
【讨论】:
请记住,您的测试正在推动您的代码。如果你发现你的测试看起来是重复的,除了左/右之类的东西,那么可能有一些底层代码左右重复。因此,您可能想看看是否可以重构代码以使用左或右并向其发送左或右标志。
【讨论】:
xunitpatterns.org 网站说“不”(复制/粘贴不行),因为在需要更新测试时会增加成本:
“剪切和粘贴”是快速编写代码的强大工具,但它 导致相同代码的许多副本,每个副本都必须是 并行维护。
为了进一步阅读,它还链接到文章
作者:Arie van Deursen、Leon Moonen、Alex van den Bergh、Gerard Kok
【讨论】:
我同意@Rob。代码需要重构。但是,如果您此时不想重构代码,那么您可以进行参数化测试。不同参数的相同测试运行。请参阅 nunit 中的 TestCase 和 TestCaseSource 属性。
【讨论】:
我有时会发现自己进行非常复杂的单元测试只是为了避免测试代码重复。我认为这样做不好。任何单个单元测试都应该尽可能简单。如果你需要复制来实现它 - 让它成为。
另一方面,如果您的单元测试有 +100500 行代码,那么显然应该对其进行重构,这将是一种简化。
当然,还要尽量避免无意义的单元测试重复,例如测试 1+1=2、2+2=4、3+3=6。如果您确实需要在不同的数据上测试相同的方法,请编写数据驱动的测试。
【讨论】: