2020幎のPHPコヌドカバレッゞの改善

コヌドカバレッゞメトリックが嘘を぀いおいるこずをご存知ですか



2003幎には、Derick RethansはリリヌスXdebugを1.2に。PHP゚コシステムで初めお、コヌドカバレッゞデヌタを収集できるようになりたした。 2004幎には、セバスチャン・バヌグマンがリリヌスPHPUnitの2圌が最初にそれを䜿甚し、。開発者は、カバレッゞレポヌトを䜿甚しおテストスむヌトのパフォヌマンスを枬定できるようになりたした。



それ以来、機胜は汎甚の独立したphp-code-coverageコンポヌネントに移動されたした。PHPDBGずPCOVが代替ドラむバヌずしお登堎したした。しかし、基本的に、開発者のコ​​アプロセスは過去16幎間倉曎されおいたせん。



2020幎8月には、のリリヌスでPHPコヌド・カバレッゞ9.0およびそれに関連するリリヌスは9.3 PHPUnitのずbehatコヌド・カバレッゞ5.0を、掚定する新しい方法カバレッゞが利甚可胜になりたした。



今日は怜蚎したす



  1. 基本のクむックツアヌ
  2. 制限事項
  3. 代替メトリック
  4. ブランチカバレッゞ
  5. カバヌパス
  6. 新しい指暙を含む
  7. どのメトリックを䜿甚したすか
  8. 新しい指暙を含めない理由はありたすか
  9. 結果


基本のクむックツアヌ



ほずんどのPHP開発者は、自動コヌドテストのアむデアに粟通しおいたす。コヌドカバレッゞの抂念は、自動テストず密接に関連しおおり、実行された、たたはテストによっお「カバヌ」されたコヌドの割合を枬定するこずです。たずえば、次のコヌドがある堎合



<?php
class PercentCalculator
{
    public function __construct(int $numerator, int $denominator)
    {
        $this->numerator = $numerator;
        $this->denominator = $denominator;
    }

    public function calculatePercent(): float
    {
        return round($this->numerator / $this->denominator * 100, 1);
    }
}


次に、以䞋に瀺すようにPHPUnitテストを蚘述できたす。



<?php
class PercentCalculatorTest extends PHPUnit\Framework\TestCase
{
    public function testTwentyIntoForty(): void
    {
        $calculator = new PercentCalculator(20, 40);
        self::assertEquals(50.0, $calculator->calculatePercent());
    }
}


テストを実行した埌、PHPUnitは、この簡単な䟋で100のカバレッゞに到達したこずを確認したす。







制限事項



ただし、䞊蚘の䟋では、小さな朜圚的なバグがありたした。$分母が0の堎合、れロ陀算゚ラヌが発生したす。それを修正しお、䜕が起こるか芋おみたしょう



<?php
class PercentCalculator
{
    public function __construct(int $numerator, int $denominator)
    {
        $this->numerator = $numerator;
        $this->denominator = $denominator;
    }

    public function calculatePercent(): float
    {
        //     ,
        //     
        //   
        return $this->denominator ? round($this->numerator / $this->denominator * 100, 1) : 0.0;
    }
}






12行目で3倀のif / elseステヌトメントが䜿甚されおいたすがnull凊理が正しいこずを確認するためのテストも䜜成しおいたせん、レポヌトには、100のコヌドカバレッゞがあるこずが瀺されおいたす。



行の䞀郚がテストでカバヌされおいる堎合、行党䜓がカバヌ枈みずしおマヌクされたす。これは誀解を招く可胜性がありたす。



行が実行されるかどうかを単玔に蚈算するだけで、他のコヌド構造でも同じ問題が発生するこずがよくありたす。たずえば、次のようになりたす。



if ($a || $b || $c) { //  ** 
    doSomething();    //     100% 
}

public function pluralise(string $thing, int $count): string
{
    $string = $count . ' ' . $thing;

    if ($count > 1) {   //     $count >= 2,  - 100%
        $string .= 's'; //      $count === 1,
    }                   //      , 

    return $string;
}


代替メトリック



バヌゞョン2.3以降、Xdebugは、䜿い慣れた行ごずのメトリックだけでなく、代替のブランチおよびパスカバレッゞメトリックも収集できるようになりたした。この機胜に぀いお話しおいるDerikのブログ投皿は、悪名高い声明で終わりたした。

「セバスチャンたたは他の誰かがPHP_CodeCoverageを曎新しお、ブランチずパスのカバレッゞを衚瀺する時間ができるたで埅぀必芁がありたす。ハッピヌハッキング

Derik Retans、2015幎1月 "


この䞍思議な「誰か」を5幎間埅った埌、私はそれをすべお自分で実装しようず決心したした。私のプルリク゚ストを受け入れおくれたSebastianBergmanに感謝したす。



ブランチカバレッゞ



最も単玔なコヌドを陀くすべおのコヌドで、実行パスが2぀以䞊のパスに分岐する可胜性がある堎所がありたす。これは、すべおのif / elseやwhileなど、すべおの決定ポむントで発生したす。これらの分岐点の各「偎」は、個別のブランチです。決定ポむントがない堎合、フロヌには1぀のブランチのみが含たれたす。



ツリヌメタファヌを䜿甚しおいるにもかかわらず、このコンテキストのブランチはバヌゞョンコントロヌルブランチず同じではないこずに泚意しおください。混同しないでください。



ブランチずパスのカバレッゞが有効になっおいる堎合、php-code-coverageで生成されたHTMLレポヌト、通垞のラむンカバレッゞレポヌトに加えお、ブランチおよびパスカバレッゞを衚瀺するためのアドオンが含たれおいたす。これは、以前ず同じコヌド䟋を䜿甚した堎合のブランチカバレッゞの倖芳







です。ご芧のずおり、ペヌゞ䞊郚のピボットボックスは、行ごずの完党なカバレッゞがありたすが、ブランチずパスのカバレッゞには適甚されないこずをすぐに瀺しおいたすパスに぀いおは、次のセクションで詳しく説明したす。



さらに、12行目はカバレッゞが䞍完党であるこずを瀺すために黄色で匷調衚瀺されたすカバレッゞが0の行は通垞どおり赀で衚瀺されたす。



最埌に、より泚意深い人は、行ごずのカバレッゞずは異なり、より倚くの行がカラヌで匷調衚瀺されおいるこずに気付くかもしれたせん。これは、分岐がPHPむンタヌプリタヌ内の実行フロヌに基づいお蚈算されるためです。各関数の最初のブランチは、その関数が入力されたずきに開始されたす。これは、関数本䜓のみが実行可胜な文字列を含むず芋なされ、関数宣蚀自䜓が実行䞍可胜ず芋なされる文字列ベヌスのカバレッゞずは察照的です。



枝を芋぀ける



PHP むンタヌプリタヌが論理的に分離したコヌドのブランチず芋なすものず、開発者のメンタルモデルずのこのような違いにより、メトリックを理解するのが難しくなる可胜性がありたす。たずえば、calculatePercentにいく぀のブランチがあるかを尋ねられた堎合、その2に答えたす0の特殊なケヌスず䞀般的なケヌス。ただし、䞊蚘のphp-code-coverageレポヌトを芋るず、この1行の関数には実際には... 4぀のブランチが含たれおいたすかPHP



むンタヌプリタヌの意味を理解するために、アップストリヌムの䞋に远加のカバレッゞレポヌトがありたす。各ブランチの衚瀺の拡匵バヌゞョンが衚瀺され、゜ヌスコヌドに隠されおいるものをより効率的に識別できたす。次のようになりたす。





キャプションは次のずおりです。「以䞋は、Xdebugが怜出したコヌドの各ブランチを衚す゜ヌスの行です。ブランチは文字列ず同じである必芁はないこずに泚意しおください。文字列には耇数のブランチを含めるこずができるため、耇数回衚瀺されたす。たた、䞀郚のブランチは暗黙的である可胜性があるこずにも泚意しおください。たずえば、ifステヌトメントは、蚘述しおいなくおも、論理フロヌに垞にelseが含たれたす。」


これはただはっきりしおいたせんが、calculatePercentに実際にどのブランチがあるかはすでに理解できたす。



  • ブランチ1は関数゚ントリから始たり、$ this->分母チェックが含たれおいたす。
  • 次に、特別な堎合が凊理されるかどうかに応じお、実行がブランチ2ず3に分割されたす。
  • ブランチ4は、ブランチ2ず3がマヌゞする堎所であり、関数の戻りず終了で構成されたす。


ブランチを゜ヌスコヌドの個々の郚分に粟神的に䞀臎させるこずは、少し緎習が必芁な新しいスキルです。しかし、読みやすく理解しやすいコヌドでそれを行うのは間違いなく簡単です。この䟋のように、コヌドが耇数のロゞックを組み合わせたスマヌトなワンラむナヌでいっぱいの堎合は、ブランチに完党に察応するすべおが数行で構造化および蚘述されおいるコヌドず比范しお、より耇雑になるず予想されたす。このスタむルで蚘述された同じロゞックは次のようになりたす。







クロヌバヌ



php-code-coverage レポヌトをClover圢匏で゚クスポヌトしお別のシステムに転送し、ブランチベヌスのカバレッゞを有効にするず、デヌタは条件付きキヌずcoveredconditionalsキヌに曞き蟌たれたす。以前たたはブランチカバレッゞが有効になっおいない堎合、゚クスポヌトされた倀は垞にれロでした。



カバヌパス



パスは、ブランチの可胜な組み合わせです。次のように、calculatePercentの䟋には2぀の可胜なパスがありたす。



  • ブランチ1、次にブランチ2、次にブランチ4;
  • ブランチ1、ブランチ3、ブランチ4の順になりたす。






ただし、倚くの堎合、たずえば、倚くの条件ずルヌプを含むコヌドでは、パスの数がブランチの数よりも倚くなりたす。php-code-coverageから取埗した次の䟋には、23のブランチがありたすが、実際には、関数には65の異なるパスがありたす。



final class File extends AbstractNode
{
    public function numberOfTestedMethods(): int
    {
        if ($this->numTestedMethods === null) {
            $this->numTestedMethods = 0;

            foreach ($this->classes as $class) {
                foreach ($class['methods'] as $method) {
                    if ($method['executableLines'] > 0 &&
                        $method['coverage'] === 100) {
                        $this->numTestedMethods++;
                    }
                }
            }

            foreach ($this->traits as $trait) {
                foreach ($trait['methods'] as $method) {
                    if ($method['executableLines'] > 0 &&
                        $method['coverage'] === 100) {
                        $this->numTestedMethods++;
                    }
                }
            }
        }

        return $this->numTestedMethods;
    }
}


23のブランチすべおが芋぀からない堎合、foreachは空のむテレヌタヌを受け入れるこずができ、垞に芋えないelseが存圚する堎合は芚えおおいおください。


はい、぀たり、100のカバレッゞには65のテストが必芁です。ブランチず同様に、php-code-coverage



HTMLレポヌトには、パスごずに远加のビュヌが含たれおいたす。生地で芆われおいるものず芆われおいないものを瀺しおいたす。



くだらない



パスカバレッゞを有効にするず、衚瀺されるメトリック、぀たりCRAPスコアにさらに圱響したす。crap4j.orgで公開されおいる定矩では、PHPでこれたで利甚できなかったパスカバレッゞのパヌセンテヌゞメトリックを蚈算の入力ずしお䜿甚したす。䞀方、PHPでは、行ごずのカバレッゞが垞に䜿甚されおきたした。カバレッゞが良奜な小さな機胜の堎合、CRAPスコアは同じたたであるか、さらには枛少する可胜性がありたす。ただし、実行パスが倚く、カバレッゞが䜎い関数の堎合、倀は倧幅に増加したす。



新しい指暙を含む



ブランチずパスのカバレッゞは、同じ基になるコヌド実行デヌタの単なる異なる衚珟であるため、䞀緒に有効たたは無効になりたす。



PHPUnit



PHPUnit 9.3+、远加的な指暙は、デフォルトでは無効になっおいるず、コマンドラむンを介しお、たたはいずれかを介しお有効にするこずができたすphpunit.xmlのコンフィギュレヌション・ファむルが、唯䞀のもずで実行しおいる堎合はXdebug。PCOVたたはPHPDBGを䜿甚しおいるずきにこの機胜を有効にしようずするず、構成の非互換性に関する譊告が衚瀺され、カバレッゞは収集されたせん。



  • コン゜ヌルで、-path -coverageオプションを䜿甚したすvendor / bin / phpunit -- path-coverage。
  • phpunit.xmlで、coverage芁玠のpathCoverage属性をtrueに蚭定したす。


<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
    <testsuites>
        <testsuite name="default">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <coverage pathCoverage="true" processUncoveredFiles="true" cacheDirectory="build/phpunit/cache">
        <include>
            <directory suffix=".php">src</directory>
        </include>

        <report>
            <text outputFile="php://stdout"/>
            <html outputDirectory="build/coverage"/>
        </report>

    </coverage>
</phpunit>


PHPUnit 9.3では、構成ファむルの圢匏が倧幅に倉曎されおいるため、䞊蚘の構造はおそらく以前ずは異なっお芋えたす。




behat-c​​ode-coverage



behatコヌドカバヌ5.0+、蚭定がで行われbehat.yml、属性が呌び出されbranchAndPathCoverage。Xdebug以倖のドラむバヌで有効にしようずするず、譊告が発行されたすが、カバレッゞは生成されたす。これは、異なる環境で同じ構成ファむルを簡単に䜿甚できるようにするためです。明瀺的に構成されおいない堎合、Xdebugで実行するず、新しいカバレッゞがデフォルトで有効になりたす。



どのメトリックを䜿甚したすか



個人的には、私Doug Wrightは可胜な限り新しいメトリックを䜿甚したす。さたざたなコヌドでそれらをテストしお、「正垞」なものを確認したした。私のプロゞェクトでは、おそらく、ハむブリッドアプロヌチを䜿甚したす。これを以䞋に瀺したす。商甚プロゞェクトの堎合、新しいメトリックに切り替える決定は、明らかにチヌム党䜓で行う必芁がありたす。私は、圌らの調査結果を自分の調査結果ず比范する機䌚を楜しみにしおいたす。



私の意芋



100のパスベヌスのカバレッゞは間違いなく聖杯であり、適甚するこずが理にかなっおいる堎合は、そうでない堎合でも努力するのに適した指暙です。テストを䜜成する堎合でも、゚ッゞケヌスなどに぀いお考える必芁がありたす。パスベヌスのカバレッゞは、問題がないこずを確認するのに圹立ちたす。



ただし、メ゜ッドに数十、数癟、たたは数千ものパスが含たれおいる堎合これは実際にはかなり耇雑なものでは珍しいこずではありたせん、数癟のテストを䜜成する時間を無駄にするこずはありたせん。 10時に停止するのが賢明です。テストはそれ自䜓が目的ではなく、リスク軜枛ツヌルであり、将来ぞの投資です。テストは報われるべきであり、それに費やされた時間テストが報われる可胜性は䜎いです。このような状況では、少なくずも各決定ポむントで䜕が起こっおいるかを確実に考えるこずができるため、適切なブランチカバレッゞを目指すのが最善です。



倚数のパスの堎合これらは正盎なCRAPで適切に定矩されおいたす、問題のコヌドがあたり機胜しないかどうかを評䟡し、それをより小さな関数すでに詳现に解析できたすに分解する合理的な方法はありたすかそうでない堎合もありたすが、それは問題ありたせん。プロゞェクトのすべおのリスクを完党に排陀する必芁はありたせん。それらに぀いお知るこずさえ玠晎らしいです。たた、機胜の境界ずそれらの分離されたナニットテストは、゜フトりェア党䜓の真の耇雑さではなく、ロゞックの人為的な分離であるこずを芚えおおくこずも重芁です。したがっお、実行パスの数が非垞に倚いずいう理由だけで、倧きな関数を壊さないこずをお勧めしたす。これは、分離によっお認知負荷が軜枛され、コヌドの認識に圹立぀堎合にのみ実行しおください。



新しい指暙を含めない理由はありたすか



はい、パフォヌマンス。Xdebugコヌドが通垞のPHPパフォヌマンスず比范しお信じられないほど遅いこずは呚知の事実です。たた、ブランチずパスのカバレッゞをオンにするず、圌が远跡する必芁のあるすべおの远加の実行デヌタにオヌバヌヘッドコストが远加されるため、すべおが悪化したす。



幞いなこずに、これらの問題に取り組む必芁があるため、開発者はphp-code-coverage内で䞀般的なパフォヌマンスを改善し、Xdebugを䜿甚するすべおの人にメリットをもたらしたした。テストスむヌトのパフォヌマンスは倧きく異なるため、これが各テストスむヌトにどのように圱響するかを刀断するのは困難ですが、文字列ベヌスのカバレッゞの収集はずにかく高速になりたす。



ブランチやパスからカバレッゞを䜜成するのは、ただ玄3〜5倍遅くなりたす。これを考慮に入れる必芁がありたす。テストスむヌト党䜓ではなく、個々のテストファむルを遞択的に有効にするか、すべおのプッシュを実行するのではなく、「より良いカバレッゞ」で倜間ビルドを有効にするこずを怜蚎しおください。



Xdebug 3は、モゞュヌル化ずパフォヌマンスに関する䜜業により、珟圚のバヌゞョンよりも倧幅に高速になるため、これらの譊告はXdebug2のみに固有のものず芋なす必芁がありたす。バヌゞョン3では、远加デヌタの収集のオヌバヌヘッドを考慮しおも、行ごずのカバレッゞを取埗するのにかかる時間よりも短い時間で、ブランチベヌスおよびパスベヌスのカバレッゞを生成できたす。





Sebastian Bergmannが実斜したテスト、DerickRethansがプロットしたグラフ




結果



新機胜をテストしお、私たちに曞いおください。圌らは圹に立ちたしたかおそらく他の蚀語からの代替の芖芚化のアむデアは特に興味深いものです。



ええず、私は垞にコヌドカバレッゞの通垞のレベルに぀いおのあなたの意芋に興味がありたす。





でPHPロシア2005幎11月29日に、私たちは、ドキュメントにないものに぀いおPHPの開発に関するすべおの最も重芁な質問を、説明したすが、䜕があなたのコヌドの新しいレベルを提䟛したす。



カンファレンスにご参加ください。PHPナニバヌスの最高のスピヌカヌにレポヌトを聞いたり質問したりするだけでなく、暖かい雰囲気の䞭でプロのコミュニケヌション぀いにオフラむンを行うこずもできたす。私たちのコミュニティTelegram、Facebook、VKontakte、YouTube。



All Articles