PHPUnitのブランチカバレッジの値が思っていたのと違った

PHPUnit 9.3からXdebugを使うと、今までのラインカバレッジに加えて、ブランチカバレッジ、パスカバレッジが計測できるようになりました。

動作確認環境

  • PHP 7.4.21
    • Xdebug 3.0.2
  • PHPUnit 9.5.8
    • php-code-coverage 9.2.6

PHPUnitの設定

ブランチカバレッジ、パスカバレッジを計測するには、 phpunit.xml にて、以下のように pathCoverage="true" を設定します。

<coverage cacheDirectory="/path/to/directory"
    pathCoverage="true">
    ...
</coverage>

用語の意味

PHPUnitのマニュアル でブランチカバレッジ、パスカバレッジの説明を確認しておきましょう。

ブランチカバレッジとは

The Branch Coverage software metric measures whether the boolean expression of each control structure evaluated to both true and false while running the test suite.

(私訳)ブランチカバレッジは、テストスイートの実行中に、各制御構造のブール式が真と偽の両方で評価されたかどうかを測定します。

パスカバレッジとは

The Path Coverage software metric measures whether each of the possible execution paths in a function or method has been followed while running the test suite. An execution path is a unique sequence of branches from the entry of the function or method to its exit.

(私訳)パスカバレッジ・ソフトウェアメトリックは、テストスイートの実行中に、関数やメソッドで可能な実行パスのそれぞれをたどったかどうかを測定します。実行パスとは、関数やメソッドの入口から出口までの、一意の分岐シーケンスのことです。

ラインカバレッジとの違い

上記から、以下のように理解していました。

  • ラインカバレッジは実行可能な行が実行されたかどうかのみを計測する
  • ブランチカバレッジは制御構造のtrue/falseの両方の場合がテスト時に実行されているかを計測する
  • パスカバレッジは全ての実行パスが実行されているかを計測する

カバレッジを計測してみる

では、実際にカバレッジを計測してみましょう。

elseがない場合

以下のクラスで確認してみます。

Sample.php:

<?php

declare(strict_types=1);

namespace Kenjis\Sample;

final class Sample
{
    public function check(int $x): bool
    {
        $ret = false;

        if ($x <= 1) {
            $ret = true;
        }

        return $ret;
    }
}

if 文が1つだけあるメソッドです。

テストコードを作成します。

SampleTest.php:

<?php

declare(strict_types=1);

namespace Kenjis\Sample;

use PHPUnit\Framework\TestCase;

class SampleTest extends TestCase
{
    /** @var Sample */
    protected $sample;

    protected function setUp(): void
    {
        $this->sample = new Sample();
    }

    public function test_CheckIfTrue(): void
    {
        $this->assertTrue($this->sample->check(1));
    }
}

if の条件が true になるケースだけテストを書きました。

if の条件が false の場合はテストがありませんので、ブランチカバレッジは 100% にはならないはずです。

カバレッジを計測してみましょう。

おかしいですね。

ラインカバレッジ、ブランチカバレッジともに 100% になっています。

一方、パスカバレッジは 50% になっています。

左のファイル名「Sample.php」の下の [branch] を押して、ブランチを確認してみます。

return が 2つありますが、どちらも実行されており(グリーン)、全行グリーンになっています。

どうもブランチカバレッジの計測結果は思っていたものと少し違うようです。バグでしょうか?

elseがある場合

Sample.php に以下のように else を追加してみます。

Sample.php:

<?php

declare(strict_types=1);

namespace Kenjis\Sample;

final class Sample
{
    public function check(int $x): bool
    {
        $ret = false;

        if ($x <= 1) {
            $ret = true;
        } else {
            $ret = false;
        }

        return $ret;
    }
}

ラインカバレッジが 80%、ブランチカバレッジが 75%になりました。

パスカバレッジは 50% で変わりません。

左のファイル名「Sample.php」の下の [branch] を押して、ブランチを確認します。

if の条件が false の場合がテストされておらず、レッドになっています。

else がある場合は、ブランチカバレッジが 100% になっておらず、想定された結果になっています。

早期returnした場合

Sample.php を以下のように変更してみます。

Sample.php:

<?php

declare(strict_types=1);

namespace Kenjis\Sample;

final class Sample
{
    public function check(int $x): bool
    {
        if ($x <= 1) {
            return true;
        }

        return false;
    }
}

ラインカバレッジ、ブランチカバレッジともに 66.67%になりました。

パスカバレッジは 50% で変わりません。

ブランチも確認しておきます。

早期returnした場合も、ブランチカバレッジが 100% になっておらず、想定された結果になっています。

まとめ

  • if 文の書き方により、テストケースが不足する場合でも、ブランチカバレッジが 100% になることがある
  • パスカバレッジについては、そのようなことはない

参考

Date: 2021/08/13

Tags: testing, phpunit