是否需要保留对简单(自包含)功能的测试?


36

考虑一下:

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

假设您为上述功能编写了各种测试,并向自己和其他人证明“它可以工作”。

为什么不删除这些测试,然后过上幸福的生活呢?我的观点是,某些功能在被证明可以正常工作后,无需对其进行连续测试。我正在寻找状态的对立点,是的,这些功能仍然需要测试,因为:...或者是的,这些功能不需要进行测试...


32
除非您更改代码,否则每个函数都是对的吗?如果它昨天工作,明天就可以工作吗?关键是软件更改。
5gon12eder

3
因为没有人会写出override_function('pow', '$a,$b', 'return $a * $b;');正确的想法...或尝试重写它来处理复数。

8
所以...嗯...“被证明是没有错误的代码” ... 它有一个错误

对于此类小型功能,您可能需要考虑基于属性的测试。基于属性的测试会自动生成测试用例,并针对预定的不变式进行测试。它们通过不变量提供文档,这使它们对于保持环境有用。对于像这样的简单功能,它们是完美的。
Martijn 2015年

6
同样,该功能是改变霍纳形式的最佳选择(($a*$x + $b)*$x + $c)*$x + $d,这种形式很容易出错,但通常速度要快得多。仅仅因为您认为它不会改变并不意味着它就不会改变。
Michael Anderson

Answers:


78

回归测试

这都是关于回归测试的

想象下一个开发人员正在查看您的方法并注意到您正在使用幻数。他被告知魔法数字是邪恶的,因此他创建了两个常数,一个常数表示第二个常数,另一个常数表示第三个常数。并不是说他正在修改您已经正确的实现。

分心后,他求反了两个常数。

他提交了代码,并且似乎一切正常,因为在每次提交之后都没有运行回归测试。

一天(可能是几周后),其他地方会出现问题。在其他地方,我的意思是在代码库的完全相反的位置,这似乎与polynominal功能无关。数小时的痛苦调试导致了罪魁祸首。在这段时间内,应用程序继续在生产中失败,从而给您的客户带来很多问题。

保留您编写的原始测试可以避免这种痛苦。分心的开发人员将提交代码,几乎立即看到他破坏了某些内容。这样的代码甚至无法实现。此外,单元测试将非常精确地确定错误的位置。解决它并不困难。

副作用...

实际上,大多数重构主要基于回归测试。做一个小的改变。测试。如果通过,一切都很好。

副作用是,如果您没有测试,那么实际上任何重构都将成为破坏代码的巨大风险。考虑到很多情况,已经很难向管理层解释应该进行重构了,在您先前的重构尝试引入了多个错误之后,这样做就更加困难了。

通过拥有完整的测试套件,您可以鼓励重构,从而获得更好,更简洁的代码。无风险,定期重构会变得很诱人。

需求变更

另一个重要方面是需求变化。可能会要求您处理复数,突然,您需要搜索版本控制日志以查找先前的测试,将其还原,然后开始添加新的测试。

为什么这么麻烦?为什么要删除测试以便以后添加?您可以将它们放在第一位。


书呆子注意:没有什么是没有风险的。;)
jpmc26 2015年

2
回归是我进行测试的首要原因-即使您的代码清楚地说明了职责,并且仅使用传递给它的任何内容(例如,没有全局变量),也很容易意外破坏某些内容。测试不是完美的,但是它们仍然很棒(并且几乎总是阻止相同的错误再次出现,大多数客户对此并不满意)。如果您的测试有效,则无需维护成本。如果他们停止工作,则可能发现了一个错误。我正在使用具有数千个全局变量的遗留应用程序-没有测试,我不敢进行许多必要的更改。
a安2015年

另一个学问注:某些“魔术”数字实际上是可以的,用常量替换它们是一个坏主意。
Deduplicator 2015年

3
@Deduplicator我们知道这一点,但是许多受过大学训练的初级程序员非常热衷于盲目遵循的咒语。“魔术数字是邪恶的”就是其中之一。另一个是“必须将多次使用的任何东西都重构为一个方法”,在极端情况下会导致大量的方法只包含一个语句(然后他们想知道为什么性能突然下降,他们从未学习过调用栈)。
jwenting

@jwenting:刚刚添加了该注释,因为MainMa写道“进行此更改没有错”。
Deduplicator 2015年

45

因为没有什么事情如此简单以至于不会有错误。

您的代码表面上看起来没有错误。实际上,它是多项式函数的简单程序表示。

除了它有一个错误...

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

$x未定义为代码的输入,并且取决于语言,运行时或作用域,函数可能无法正常工作,可能导致无效结果或使飞船崩溃


附录:

虽然您可能暂时认为自己的代码错误是免费的,但这种情况还能保留多长时间却很难说。尽管可能会争辩说,为这样琐碎的代码编写测试是不值得的,但已经编写了测试,工作就完成了,删除它就删除了有形的安全保护措施。

需要特别注意的是代码覆盖率服务(例如Coveralls.io),可以很好地表明测试套件提供的覆盖率。通过覆盖每一行代码,您可以得出执行的测试数量(如果不是质量)的一个不错的衡量标准。结合大量的小型测试,这些服务至少会告诉您在错误发生时不要在哪里寻找错误。

最终,如果您已经编写了测试,请保留该测试。因为删除它所节省的空间或时间很可能比以后出现错误时要付出的技术债务少得多。


还有另一种解决方案:切换到非动态非弱语言。单元测试通常是不够强的编译时间验证解决方法,有些语言是一种很好的在这个en.wikipedia.org/wiki/Agda_(programming_language)
书斋

21

是。如果我们可以百分百确定地说:该函数将永远不会被编辑,并且永远不会在可能导致其失败的环境中运行-如果我们可以这样说,我们可以放弃测试并在每一次测试上节省几毫秒CI构建。

但是我们不能。或者,我们不能提供许多功能。与始终努力确定我们满足的置信度阈值以及我们对任何给定函数的不变性和无误性具有多少置信度相比,始终保持运行所有测试的规则要简单得多。

而且处理时间很便宜。节省下来的毫秒数,甚至是倍增的时间,相加起来不足以证明花些时间询问每个函数:我们是否有足够的信心相信我们不需要再次进行测试?


我认为这是您提出来的一个很好的观点。我已经可以看到两个人花了很多时间争论要为一些小的功能进行测试。
卡波尔

@Kapol:不是争论,是一次会议,确定哪些可以去,哪些可以保留,应该使用什么标准来决定,以及在哪里记录这些标准,以及谁签署最终决定……
jmoreno

12

其他答案中的所有内容都是正确的,但我会再添加一个。

文献资料

单元测试(如果编写得当)可以向开发人员确切说明功能的作用,其输入/输出的期望值,更重要的是,可以预期该功能的行为。

它可以使发现错误更容易,并且减少混乱。

并不是每个人都记得多项式,几何甚至代数:)但是签入版本控制的良好单元测试会为我记住。

有关如何作为文档有用的示例,请查看Jasmine简介:http : //jasmine.github.io/edge/introduction.html 加载几秒钟,然后滚动到底部。您将看到完整的Jasmine API记录为单元测试输出。

[根据来自@Warbo的反馈进行更新]保证测试也是最新的,因为如果没有更新,它们将失败,如果使用CI,通常会导致构建失败。外部文档的更改与代码无关,因此不一定是最新的。


1
将测试用作您隐式保留的文档(一部分)有一个很大的优势:它们会被自动检查。其他形式的文档,例如 网页上的解释性注释或代码片段可能随着代码的发展而过时。如果单元测试不再准确,则将触发失败。茉莉花页就是一个极端的例子。
华宝(Warbo)

我想补充一下,某些语言(例如D和Rust)将其文档生成与单元测试集成在一起,因此您可以将同一段代码编译成单元测试并插入到HTML文档中
Idan Arye

2

现实检查

我一直在充满挑战的环境中进行测试,这在预算和进度计划中是“浪费时间”,然后在客户处理错误后才是“质量保证的基本部分”,所以我的观点比其他人更易变。

您有预算。您的工作是在预算范围内获得最好的产品,无论您将什么“最佳”定义都拼凑在一起(定义这个词并不容易)。故事结局。

测试是库存中工具。您应该使用它,因为它是一个很好的工具,具有保存数百万甚至数十亿美元的悠久历史。如果有机会,您应该向这些简单功能添加测试。它有一天可以拯救您的皮肤。

但是在现实世界中,由于预算和进度限制,这种情况可能不会发生。不要让自己成为程序的奴隶。测试功能很好,但是在某些时候,您的工作时间可能比用代码而不是代码来编写开发人员文档更好,因此下一个开发人员不需要那么多测试。或者,最好将其花费在重构代码库上,这样您就不必像维护野兽一样困难。或者,也许最好把这段时间花在与您的老板讨论预算和时间表上,以便他或她更好地了解下一轮融资即将到来时他们在竞标什么。

软件开发是一种平衡。始终计算您正在做的任何事情的机会成本,以确保没有更好的方法来度过您的时间。


4
在这种情况下,已经存在一个测试,因此问题不在于是否编写它们,而是是否要提交它们并在每个构建或发行版中运行它们,或者存在运行所有测试的时间表。
圣保罗Ebermann

0

是的,保持测试,使其运行并保持通过。

单元测试可以保护您(和其他人)免受自己(和自己)的伤害。

为什么保持测试是个好主意?

  • 面对新需求和其他功能,验证先前需求的功能
  • 验证重构练习是否正确
  • 内部文档-这是预期使用代码的方式
  • 回归测试,事情变了
    • 所做的更改是否破坏了旧代码?
    • 更改是否需要进一步的更改请求或对当前功能或代码的更新?
  • 鉴于测试已经编写,请保留它们;是已经花费的时间和金钱将进一步降低维护成本

2
与提供的其他答案相比,这似乎没有提供任何实质性的内容。

1
较早发布时出现问题,但回想起来,这是一个不错的总结。我将其删除。
Niall

-1

开发人员文件

  • 我(作为另一个开发人员)如何知道已经过测试?
  • 如果我想修复自包含函数中的错误,我怎么知道我没有引入您已经考虑过的错误?
  • 复杂性指标:测试数量可以很好地衡量事物的复杂程度。这可能表明您不应该触摸它,因为它既成熟又稳定,或者它肿胀且耦合。

用户文件

  • 待处理的文档较差时,我可以查看是否接受了{零,负值,空集等},以及期望的返回值是多少。
  • 它还提供了一个很好的示例,说明我应该如何使用对象/函数

1
在先前的回答中提出和解释的观点看来,这似乎没有提供任何实质性的内容。即使是“很好的总结” 已经被公布(合我的口味是不那么好的,但很好哦)
蚊蚋
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.