当项目编码标准与单元测试代码覆盖率发生冲突时,我应该怎么做?


What should I do when project coding standards conflict with unit test code coverage?

我正在做一个学习经验的个人项目,同时也要实现一个体面的代码体。这种教育的一部分是单元测试,并使其成为一个体面的代码体。我最近深入研究了PHPUnit和它的代码覆盖工具。

我遇到过一个特殊实现的情况,其中使用的编码标准导致代码覆盖率丢失。在这个特殊的实例中,打破所使用的编码标准会导致代码覆盖率从88%跃升到94%。

在一个方法中,我有如下两行:

    // .. some data validation stuff
    trigger_error('Error validating the stuff', E_USER_WARNING);
}

数据验证和其他东西在这里并不重要;}为。现在,当单元测试检查这行代码时,在}之前的行抛出PHPUnit_Framework_Error,因为代码实际上从未继续到大括号的末尾,因此代码覆盖从未捕获这行。

如果我这样做

    // .. some data validation stuff
    trigger_error('Error validating the stuff', E_USER_WARNING);}

代码覆盖率提高了6%。我已经尝试将PHPUnit_Framework_Error_Warning::$enabled设置为false,但随后我在我的终端中得到一个丑陋的,预期的错误消息,因为我希望这个项目最终由我自己以外的人使用,单元测试上的错误消息是不可接受的。

此外,我真的希望我的编码风格能够一致地实现。代码风格的冲突可能会在进一步阅读代码时跳出来,这意味着我还必须添加一个可怕的注释来解释为什么要移动大括号……可能在很多地方。

我想我的问题是:

  1. 是否有一个PHPUnit的设置,允许使用1TBS,仍然得到一个测试抛出一个异常,或触发一个错误,直接在}之前?
  2. 是遵循编码标准更重要还是获得代码覆盖率的提升更重要?(尽管boost实际上只是解释器在额外的}上运行)

不要被数字所困扰。你知道报道的数字是假的,你的覆盖率比报道的要高,所以为什么要担心呢?测试覆盖所有有意义的代码比实现100%的代码覆盖率更重要。

如果您觉得编码标准很重要,并且这个标准看起来很重要,那么不要为了一个数字而牺牲可读性。

回复原来的问题:

你的编码指南不应该仅仅是100%的代码覆盖率。如果你在一行中编写整个应用程序,你会得到100%,但只是因为PHPUnit/xDebug当前报告LINE覆盖率而不是STATEMENT覆盖率


对我来说,手头的问题是另一个:

你有一个方法,在结束时无条件触发一个错误。

所以你没有使用异常。所以问题可能是:

"Does trigger error get into the way of having 100% code coverage / complete tests"

它不。通常trigger_error在引入异常之前使用,或者在保持其遗留使用一致的项目中使用。仍然是php的核心函数触发它们,但所有这些都有返回值来实际检查错误。

所以当使用trigger_error时,它归结为:

  • 如果你想处理代码中的错误,使用返回值
  • 如果你不想处理错误使用日志记录,而不是触发错误

如果你有一个返回值,你可以为这个方法编写有意义的测试。

一个检查trigger_error的测试用例和一个断言返回值的测试用例(调用带有错误抑制的方法)

看一下 Example 4.12 from the phpunit docs


对我来说,这是一个情况,你看到的东西是错误的,但问题不在测试,但在代码

你需要在你的测试中做一些丑陋的事情,你开始考虑代码覆盖率、编码规则和一个"变通方法"。通常当这种情况发生在我身上时,我会从代码中退一步,看看这个问题是否可以在另一个层面上解决。有时这会导致我发现代码中的设计缺陷。


回到刚才的问题:

实现100%代码覆盖率的关键在于您拥有"干净的代码";并不是说你有100%的代码覆盖率。为之奋斗的全部意义在于,你实际上仔细检查你的代码,看看为什么你不能在使用类的时候到达你的代码行。这可能是设计问题。

修复这些缺陷可以很容易地实现100%的覆盖率。用黑客手段获取"数字"正确通常比完全不关心更糟糕。你不仅没有解决问题,还把它掩盖了。

您的覆盖率误差差来自PHPUnit及其相关机器以"源代码行"的形式计算代码覆盖率。如果能做好的话,那样做并没有错。

因为XDebug机制只理解它实际执行的行号,我不认为它可以"得到正确的",因为你的风格将"源行"放在它不能"执行"的地方。

考虑如果你写:

  1:   foo() {  return; 
  2:        ...1000 comment lines...
  1002:      }

XDebug看到的是第1行返回的执行,所以一行被清楚地覆盖了(不完全正确,参见下面的例子);PHPUnit看到的(我认为)是1002行代码。我想如果它自己经营的话,会给你0.1%的保险。(在较大的程序中,您不太可能有如此极端的代码,并且平均值会变得更好)。

所以你得到的覆盖率,严重依赖于你的编码风格。一个更模糊的变体:

  1:    bar() {   if (a) { $x=2; if (b) { $y=3 } } }

由于XDebug显然只跟踪行号,如果bar()被调用,第1行将被执行,并且您有一行源代码,因此您将获得100%的覆盖率。我相信图形显示工具也会将整条线显示为"覆盖"。但是,如果条件(a)为false,第二个条件语句根本没有机会执行。您可以合理地争辩说,只覆盖了50%的代码(第一个如果,但不是第二个)。图形显示不正确,因为$y=3显示为覆盖,但没有,所以您对细节产生了误导。

要获得更准确的数字,工具需要知道的是单独控制的可执行代码块的数量(在编译器文献中称为"基本块"),以及执行的此类块的数量。在foo示例中,只有一个基本块,如果它被执行,你应该100%覆盖所有的基本块,如果没有,你应该0%,不管你如何格式化你的代码。在条形图的例子中,有两个基本块(函数条目和第二个if中的$y=3),如果第二个块没有执行,你应该得到50%的覆盖率,如果执行了,你应该得到100%的覆盖率,不管它是如何格式化的。

因为我不认为PHPUnit/XDebug对基本块有任何理解,我不明白它如何能给出更准确的数字。(从统计数据来看,它们相当接近;你说你偏离了6%,如果你的目标是达到80%,你可能需要达到74-86%,这取决于你的格式,以获得80%的"打印"覆盖率。

您为基本块解决方案所付出的代价是,覆盖范围不是根据"源行",而是根据可执行块。我认为后者是更合适的答案,但是你的里程可能会有所不同,老板们往往会被这种区别所迷惑。

我们的PHP测试覆盖工具具有基本块的概念,通过对源代码进行编译器精确解析获得。它将产生更准确的数字。它的显示工具也理解一行中可能有多条语句,并将正确地为已执行和未执行的部分涂上颜色。

在Xdebug改进到可以检测到}为不可执行之前,您可以使用忽略代码覆盖注释来两全美。

    // .. some data validation stuff
    trigger_error('Error validating the stuff', E_USER_WARNING);
// @codeCoverageIgnoreStart
}
// @codeCoverageIgnoreEnd

我提交了一个功能请求,添加一个单行注释,这将使它更美观。

} // @codeCoverageIgnore

但也许你应该听从斯廷斯的建议,不要过于热衷于达到100%的覆盖率。