高品質のテストを書く

Susan Sarandon
Susan Sarandonオリジナル
2024-12-18 12:39:16297ブラウズ

Writing high quality tests

残念ながら、テストは依然として多くの組織で当然の注目を集めていません。開発者はテストをまったく書いていないことに罪悪感を感じているように感じることがありますが、同時にテストコードが適切にレビューされていないことがよくあります。代わりに、レビューでよくチェックされるのはテストがあるかどうかだけですが、テストがあるだけでは十分ではありませんので、これは残念です。実際には、プロジェクト内の他のすべてのコードと少なくとも同じ品質、あるいはそれ以上の品質である必要があります。そうしないと、テストがあまりにも頻繁に失敗したり、理解しにくくなったり、実行に時間がかかりすぎたりするため、実際にテストを行うのをためらう可能性があります。リポジトリ モックの代わりにメモリ内実装を使用することについては、ブログ投稿でこれらの点のいくつかをすでに説明しました。ここで、テストを作成するときに私が気をつけている他の、より一般的なことについて説明したいと思います。

ミニマリズムが鍵です

スタック オーバーフローでは、質問に最小限の再現可能な例を追加するよう求められます。私の意見では、これはまったく同じ理由でテストを作成する場合にも非常に良いアドバイスです。特に、テストを書いてから数か月後にテストを読む場合、起こっていることが少なければ、何が起こっているのかを完全に理解するのがはるかに簡単です。したがって、テストに絶対に必要なコードのみを記述してください。簡単だからという理由だけでさらにコードを追加する誘惑に抵抗してください。ただし、テスト コードはもちろん完全である必要があります。つまり、テストには必要なだけ多くの行が含まれますが、できる限り少なくする必要があります。

100% のコード カバレッジを目指します

これは不人気な意見かもしれませんが、多くの人がこれを悪い習慣だと考えているようですが、100% のコード カバレッジを目指すのは完全に理にかなっていると思います。

チームは、より低い値で妥協することがあります。コードカバレッジは90%。しかし、それは私にとってあまり意味がありません。まず第一に、これらの数値はすべてやや恣意的なものであり、データを使用してバックアップするのは困難です。また、新しいコードを作成する場合、そのしきい値を通過するためにすべてのコードをテストする必要はありません。そして、誰かがなんとかカバレッジを上げることができたとしても、次の人はコード カバレッジを 90% 以上に保ちながらまったくテストを書かずに済んでしまう可能性があり、その結果、誤った自信が生まれてしまいます。

私がよく聞く言い訳の 1 つは、ゲッターやセッターのような単純な関数のテストを書くのは意味がないというものです。そして驚くべきことに、私もそれに完全に同意します。しかし、ここに落とし穴があります: どのテストも実際にこれらのゲッターとセッターを使用しない場合、おそらくそれらを使用する必要はありません。 したがって、100% のテスト カバレッジを達成することがいかに難しいかについて不平を言う代わりに、次のようにします。そもそも、必要のないコードは書かないほうがよいでしょう。これにより、コードの各行に伴うメンテナンスの負担も回避されます。

ただし、小さな落とし穴があります。コードが時々奇妙な動作をするため、テスト実行中に実行された場合でも、コード カバレッジ ツールが一部の行を未カバーとしてマークする可能性があります。このような状況にはあまり遭遇しませんでしたが、これを機能させる方法がない場合は、コードカバレッジから除外します。例えば。 PHPUnit では、codeCoverageIgnore アノテーションを使用してこれを行うことができます:

<?php

class SomeClass
{
    /**
     * @codeCoverageIgnore
     */
    public function doSomethingNotDetectedAsCovered()
    {

    }
}

この方法では、この関数はコード カバレッジ分析に含まれません。これは、コード カバレッジ 100% に到達する可能性がまだあることを意味します。また、その値もチェックし続けます。別の方法としては、100% よりも低い値に落ち着くこともありますが、その場合、上記と同じ問題が発生します。他のコードもテストでカバーされていない可能性があり、それが見逃される可能性があります。

とはいえ、コード カバレッジが 100% であっても、コードにバグがないという保証はありません。しかし、アプリケーション コード内に発見されていない行がある場合、その行の潜在的なエラーを検出するためにテストに変更を加えているわけではありません。

良いアサーションを書く

テストが書かれる理由は、コードの特定の動作を主張したいからです。したがって、アサーションはテストの非常に重要な部分です。

もちろん、アサーションを作成するときに最も重要な考慮事項は、コードの動作を正しくテストすることです。しかし、非常に近いのは、コードが失敗したときにアサーションがどのように動作するかです。何らかの理由でアサーションが失敗した場合、問題は開発者にとって可能な限り明らかである必要があります。これが明らかな状況は、この Symfony プル リクエストで現在取り組んでいる状況です。 Symfony にはassertResponseStatusCodeSame メソッドが付属しており、機能テストで応答のステータス コードをチェックできます:

<?php

declare(strict_types=1);

class LoginControllerTest extends WebTestCase
{
    public function testFormAttributes(): void
    {
        $client = static::createClient();

        $client->request('GET', '/login');
        $this->assertResponseStatusCodeSame(200);

        $this->assertSelectorCount(1, 'input[name="email"][required]');
    }
}

このテストの問題は、ステータス コードが 200 でない場合に生成される出力です。テストは通常​​開発環境で実行されるため、この URL にアクセスすると Symfony はエラー ページを返し、assertResponseStatusCodeSame メソッドはアサーションが失敗した場合の応答全体。この出力は HTML だけでなく CSS と JavaScript も返すため、非常に長くなります。また、スクロールバック バッファーが文字通り小さすぎてメッセージ全体を読むことができないためです。

これは私がこれまでに遭遇した最悪の例ですが、コード内で間違ったアサーションが使用されている場合は迷惑な場合もあります。上記のassertSelectorCount アサーションの出力を見てみましょう。指定されたセレクターが正確に 1 つの要素を生成しない場合、このアサーションは失敗し、次のメッセージが表示されます。

Failed asserting that the Crawler selector "input[name="email"][required]" was expected to be found 1 time(s) but was found 0 time(s).

発生している問題についてかなり良いアイデアが得られます。ただし、アサーションは別の方法で書くこともできます (自宅ではこれを行わないでください)。

<?php

class SomeClass
{
    /**
     * @codeCoverageIgnore
     */
    public function doSomethingNotDetectedAsCovered()
    {

    }
}

これはまったく同じことを行うので、どのバリアントが使用されるかは問題ではない、と主張する人もいるかもしれません。電子メールに必須の入力フィールドが 1 つも存在しない場合、次のメッセージが表示されるため、これは真実からは程遠いはずです。

<?php

declare(strict_types=1);

class LoginControllerTest extends WebTestCase
{
    public function testFormAttributes(): void
    {
        $client = static::createClient();

        $client->request('GET', '/login');
        $this->assertResponseStatusCodeSame(200);

        $this->assertSelectorCount(1, 'input[name="email"][required]');
    }
}

これはまったく役に立ちません。問題の解決に取り組む人は、まず問題が実際に何なのかを把握する必要があります。これが示しているのは、常に適切なアサーションを使用する必要があり、PHPUnit にはあらゆる種類のユースケースに適合する多くのアサーションが付属しているということです。場合によっては、カスタム アサーションを作成することが意味があることもあります。

近年人気が高まっている比較的新しい主張は、スナップショット テストです。特にフロントエンドプロジェクトに取り組み始める場合には、大いに役立つようです。以前はReactでよく使っていました。主な要点は、テストが次のようになることです:

Failed asserting that the Crawler selector "input[name="email"][required]" was expected to be found 1 time(s) but was found 0 time(s).

魔法は toMatchSnapshot メソッドで起こります。最初の実行では、ツリー変数の内容が別のファイルに書き込まれます。以降の実行では、ツリー値の新しい値と、以前に別のファイルに保存されていた値が比較されます。何かが変更された場合、テストは失敗し、スナップショットを再度更新するオプションとともに差分が表示されます。これは、テストを瞬時に修正できることを意味します。

これはとても良いことのように聞こえますが、いくつかの欠点もあります。まず、コンポーネントのレンダリングされたマークアップが変更されるたびにテストが失敗するため、スナップショットは非常に脆弱です。第二に、作成者が実際に何をテストしたかったのかが説明されていないため、テストの意図が隠されています。

しかし、私が本当に楽しかったのは、コンポーネントを変更するたびに、そのコンポーネントを使用している他のすべてのコンポーネントが思い出されることでした。これは、次回の実行ではそれらのスナップショットがすべて失敗したためです。このため、私はコンポーネントごとに少なくとも 1 つのスナップショット テストを行うことを好みました。

結論

要約すると、テストの品質を向上させるためにすぐに始められることがいくつかあると思います。

  • テスト内のコードは絶対に必要な最小限にとどめてください
  • 100% のコード カバレッジを目指し、テストできない場合はコード カバレッジ メカニズムからコードを適切に除外します
  • テストが失敗した場合に適切なエラー メッセージを取得するには、正しいアサーションを使用します

私の意見では、これらのいくつかのルールに従うだけで、すでに大きな違いが生まれ、コードベースでの作業を長期間楽しむのに役立ちます!

以上が高品質のテストを書くの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。