PHPUnit和C.R.A.P指数

14

我正在使用php undercontrol,代码浏览器报告每个setter/getter的CRAP指数错误,例如以下代码:

public function getFoo()
{
    return $this->_foo;
}

这些 getter/setter 已经被单元测试覆盖了,因为没有 if/for/switch/foreach 所以它们的复杂度为零。那么为什么我的代码 CRAP 指数却是 1 呢?

另外,我想说一下,自问自答可能是因为复杂度为零,但我的主要问题是每个 getter/setter 都会生成一个警告,因为 CRAP 指数的原因。有没有办法告诉 PHPUnit/PHP Code Coverage 对于复杂度为零的函数将 CRAP 置为 0 呢?


我们从phpUnderControl切换到了Jenkins。它更加可配置,正在积极开发中,并且有一个适用于PHP项目的项目模板。它允许您设置阈值CRAP值,以触发警告和构建失败。 - David Harkness
3个回答

32

CRAP 分数的最小值为 1,而不是 0。这是因为 CRAP 算法是这样的:

CRAP(m) = comp(m)^2 * (1 – cov(m)/100)^3 + comp(m)

函数的最小圈复杂度(comp)值为1。所以问题不在于phpunit,而是标记CRAP为1的问题。

一般来说,您希望将CRAP阈值设置在5左右,如果太低,您可能只需使用简单的代码覆盖指标(并力求达到100%),因为复杂性因素几乎不起作用。 CRAP >= 30意味着无论测试多少次,您的方法都是很差的。

圈复杂度通常可以手动计算(但有不止一种定义):

  • 为函数调用添加1分
  • 为每个循环添加1分
  • 为每个分支添加1分

你说得对,最小值是1... 这似乎是php_codebrowser的一个bug,它会突出显示那些即使它们没有问题的行。请参阅 https://github.com/mayflowergmbh/PHP_CodeBrowser/issues/50 - RageZ
一个有三个方法得分为3的类,如果它有12个CRAP,那会怎么样呢?(我认为这是由于返回路径的数量导致的) - MrMesees
@MrMesees 如果类完全覆盖,则12的CRAP是可以容忍的。如果您在12且类没有100%的覆盖率,请再覆盖一些,您可能会降到10或8。但是,如果经过全面测试并且您得到12分,您需要查看是否有正当理由或者您的类执行了两个不同的角色,这应该“建议”您将其拆分为2个单独的类。如果您的类来自遗留系统并且得到40或80分,请立即进行重构。甚至未经测试的类报告200或400,请立即进行重构。 - Xavi Montero

2
CRAP 只是一个指标。仅凭它本身,它就像“一段绳子有多长?”一样没有太大用处。除了是一个答案和一个模棱两可的问题,它还是一个模棱两可的答案。
如果您知道它要衡量什么,那么您可以将其用作复杂性的基本指标。要更好地利用它,您需要相当多的经验来比较实现。之后,您最好需要比较同一事物的实现。之后,您需要对正在测试的代码非常熟悉,而且如果您确实这样做了,那么您比CRAP分数更有深入洞察力。
它越高,改善可测试性(包括效率)和变更点的可能性就越高。但是,只有在8000或9000以上的得分情况下,某些东西被认为是绝对的CRAP的概率才开始接近确定性。像处理从已解析的XML文档中的节点到无法以决定性方式改进的函数一样基本的事情,即使表现良好,也可能会产生数百种复杂性。
这有点像花钱。对于给定的目的,您可能必须花费最少的金额。它可能是100万美元,也可能是1000美元,但无论目的是什么,我们倾向于认为支出越高,过度支出的可能性就越大。但是也许需要很高的支出,也许您正在购买游艇。单纯地强制降低数字不仅是在另一个方向上犯同样的错误,而且是真正的危险的。71人在格伦费尔塔(Grenfell Tower)因这种灾难性的思维错误被烧死或窒息。
您不应该假设降低CRAP会提高可测试性或可维护性。很多时候,高CRAP仅报告了强制性复杂性的度量。您可以在技术上减少CRAP,玩弄数字,同时降低可测试性、可维护性和可读性。您只能通过实际改进来提高这些事情。CRAP甚至不是一种可靠的改进度量。有时改进后CRAP可能会下降。有时它可能会上升。指标游戏的问题在于人们经常只是转移问题或隐藏作为复杂性指标的东西。
常见的例子是使用映射而不是switch语句或if语句。我自己也会这样做。但是我们忘记了我们正在转移复杂性。在这种情况下,我们可以使用一个维护良好且可靠的映射实用程序库。如果您包括该映射函数与一些if语句进行比较,则整体复杂性度量将非常高。当您没有这样的工具可供使用时,您需要非常小心地降低复杂性。例如,如果您试图完全消除if语句和for循环,祝您好运。
循环复杂度确实可以很好地反映出测试速度是否可以提高。这在一些情况下是微不足道的,例如在函数中有两个if语句。如果第二个if语句所依赖的状态基于第一个if语句是否匹配而异,则必须多次运行第一个if语句(4次而不是2次)。当您组合代码时,可能的排列组合会呈非线性增长。如果您有8个函数,每个函数都需要一个布尔值并返回一个布尔值,那么您可以单独测试每个函数以获得8乘2(布尔值有两个可能的输入值)个测试(16个测试)。但是,如果您将所有这些函数组合起来,则有256个不同的可能输入组合。循环复杂度可以帮助指示可能出现这种情况的地方。
它还在某种程度上表明了您需要多少测试才能涵盖函数和参数。如果您有一个具有一个布尔参数和一个基于它的if语句的函数,则至少需要两个测试才能涵盖全部代码。两个布尔参数和两个if语句,然后根据if语句是否嵌套,需要四个或三个测试。在最坏的情况下,每个添加到if之后的if都会使您需要测试的组合数量增加两倍的幂。
这可能会导致冲突,迫使您过早地分段代码,因为在运行时您可能永远不会真正遇到该问题。通常情况下,除非测试开始消耗大量资源或实际上变得不成比例的难以处理,否则您不应该担心这一点。在这种情况下,您将不依赖于CRAP而是了解代码的执行和基准。
CRAP可能会出错,因为它对复杂性做出了相当幼稚的猜测。使用它越接近悲观或最坏情况的估计。我正在查看一段具有高CRAP的代码,但它无法区分if($ constantInScope)etc; etc; if($ constantInScope)etc; 与if($ varA)etc; etc; if($ varB)etc; 。
如果单个函数在执行路径方面确实有数百、数千或数百万种可能的结果,则这可能是测试存在问题的一个很好的迹象。尽管这可能是不可避免的。相反,它可能比指示的要容易测试得多。循环复杂度的有限能力可能是为什么CRAP还包括覆盖率的平衡的原因。如果您进行了测试并获得了很高的覆盖率,则它可能并不像循环复杂度所想的那样难以测试,特别是要记住仅通过执行路径使事情无法完全测试是有可能的,尽管非常罕见。
CRAP无用的一个简单例子是展开循环并将if语句替换为数学语句等以减少CRAP。

1

这真的是一个警告吗?一般来说,警告的阈值要比1高得多(可能在30左右)。有一个很好的SO帖子在这里展示了如何计算这个数字。在我的phpunit设置中似乎有几个硬编码的值为CRAP 30。

根据CRAP指数的创造者Alberto Savoia所说:

“C.R.A.P.(更改风险分析和预测)指数旨在分析和预测维护现有代码库所需的工作量、痛苦和时间。”

最小的CRAP数字将是100%覆盖率的代码的圆形复杂度。这个想法是,在复杂的代码中进行更改比简单的代码更容易出现问题。


实际上这只是一个通知,但在每个getter和setter上都有一个通知会让人感到相当烦人。 - RageZ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接