web-dev-qa-db-ja.com

テストを整理するためのPHPUnitベストプラクティス

現在、プロジェクトのphpunitテストをゼロから始めます。だから、私はいくつかのプロジェクト(Zendなど)を見て、彼らがどのように物事をやっているか、どのようにテストを整理しているかを見ていた。

ほとんどのことは非常に明確であり、テストスイートを適切に整理する方法に問題があるだけです。 Zendには、他のテストスイートをロードするAllTests.phpがあります。
クラスをよく見ると、PHPUnit_Framework_TestSuiteを使用してスイートオブジェクトを作成し、他のスイートを追加していますが、PHPUnitバージョンでテストを整理するためにPHPUnitのドキュメントを見ると、3.4 XMLまたはFileHierarchyの説明のみ。クラスを使用してテストを整理するものは削除されました。
このメソッドが非推奨であり、Zendのようなプロジェクトがまだそれを使用していることを何も発見していません。

しかし、廃止された場合、xml構成と同じ構造でテストを整理するにはどうすればよいですか?すべてのテストを実行しても問題はありませんが、少数のテストのみを実行したい場合、どのように(xmlで)テストを編成するのでしょうか。実行するテスト/テストスイートのみを指定する複数のxmlを作成することはできますか?

したがって、アプリケーションのmodule1とmodule2のみをテストする場合は、それぞれに追加のxmlを用意し、それらのモジュール(モジュールで使用されるクラス)のみに対してテストスイートを定義します。また、すべてのテストのテストスイートを定義するものもありますか?

または、特定のテストで@groupアノテーションを使用して、それらをmodule1またはmodule2にマークする方が良いでしょうか?

いくつかのベストプラクティスを教えてくれてありがとう。

66
enricog

まず、マニュアルにリンクしてから、現場で見たり聞いたりしたことを説明します。

phpunitテストスイートの整理

ファイルシステム内のモジュール/テストフォルダーの構成

私の推奨するアプローチは、ファイルシステムをxml構成と組み合わせることです。

tests/
 \ unit/
 | - module1
 | - module2
 - integration/
 - functional/

シンプルなphpunit.xmlで:

<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

必要に応じてテストスイートを分割できますが、プロジェクトごとに選択できます。

phpunitを実行するとすべてのテストが実行され、phpunit tests/unit/module1を実行するとmodule1のすべてのテストが実行されます。

「ユニット」フォルダーの構成

ここで最も一般的なアプローチは、source/フォルダー構造にtests/unit/ディレクトリー構造をミラーリングすることです。

とにかくProductionClassごとに1つのTestClassがあるので、私の本ではそれが良いアプローチです。

ファイル編成内

  • ファイルごとに1つのクラス。

1つのファイルに複数のテストクラスがある場合、とにかく動作しませんので、その落とし穴を避けてください。

  • テスト名前空間はありません

追加のuseステートメントが必要なため、テストの記述がより冗長になります。したがって、testClassはプロダクションクラスと同じネームスペースに配置する必要がありますが、PHPUnitが強制することはありません。欠点なしで簡単にできることがわかりました。

いくつかのテストのみを実行する

たとえば、phpunit --filter FactoryはすべてのFactoryTestsを実行し、phpunit tests/unit/logger/はロギング関連のすべてを実行します。

課題番号、ストーリーなどに@groupタグを使用できますが、「モジュール」にはフォルダーレイアウトを使用します。

複数のxmlファイル

必要な場合は、複数のxmlファイルを作成すると便利です。

  • コードカバレッジのないもの
  • 1つは単体テスト専用です(ただし、機能テスト、統合テスト、または長期実行テスト用ではありません)
  • 他の一般的な「フィルター」ケース
  • たとえば、PHPBB3は their phpunit.xmls

テストのコードカバレッジ

テストで新しいプロジェクトを開始することに関連しているため:

  • 私の提案は@coversタグを使用することです 私のブログで説明されているように (ユニットテストの場合のみ、すべての非公開関数を常にカバーし、常にカバータグを使用します。
  • 統合テストのカバレッジを生成しないでください。それはあなたに安心感を与えます。
  • 常にホワイトリストを使用して、すべてのプロダクションコードを含めて、数字が嘘をつかないようにしてください!

テストの自動読み込みとブートストラップ

テストのために自動ロードを行う必要はありません。 PHPUnitがそれを処理します。

<phpunit bootstrap="file">属性を使用して、テストブートストラップを指定します。 tests/bootstrap.phpはそれを置くのに良い場所です。ここで、アプリケーションのオートローダーなどをセットアップできます(または、アプリケーションについてbootstrapを呼び出します)。

概要

  • ほとんどすべてにxml構成を使用します
  • 単体テストと統合テスト
  • ユニットテストフォルダーは、アプリケーションのフォルダー構造をミラーリングする必要があります
  • Specifテストのみを実行するには、phpunit --filterまたはphpunit tests/unit/module1を使用します
  • Get goからstrictモードを使用し、決してオフにしないでください。

見るサンプルプロジェクト

104
edorian

基本的なディレクトリ構造

私は、テスト対象のコードのすぐ隣に、テスト対象のコードを持つファイルとは少し異なるファイル名で文字通り同じディレクトリにテストコードを保持することを試みてきました。これまでのところ、私はこのアプローチが好きです。コードとテストコードの間でディレクトリ構造を同期させるために時間と労力を費やす必要がないという考え方です。したがって、コードが入っているディレクトリの名前を変更する場合、テストコードのディレクトリ名を探して変更する必要もありません。これにより、コードのすぐ隣にあるテストコードを探す時間を短縮できます。これにより、最初にテストのあるディレクトリを見つける必要がなく、テストを作成しているディレクトリと一致する新しいディレクトリを作成する必要がないため、テストコードを使用してファイルを作成する手間が少なくなります。次に、テストファイルを作成します。そこにテストファイルを作成するだけです。

これの大きな利点の1つは、他の従業員(これを行うことはないのであなたではない)が、作業が多すぎるため、最初からテストコードを書くことを避ける可能性が低いことを意味します。既存のクラスにメソッドを追加する場合でも、テストコードを見つける際の摩擦が少ないため、既存のテストコードにテストを追加する気にならないでしょう。

欠点の1つは、テストを伴わずに本番コードをリリースするのが難しくなることです。厳密な命名規則を使用する場合でも、それは可能かもしれません。たとえば、ClassName.php、ClassNameUnitTest.php、およびClassNameIntegrationTest.phpを使用しています。すべての単体テストを実行する場合、UnitTest.phpで終わるファイルを検索するスイートがあります。統合テストスイートも同様に機能します。必要に応じて、同様の手法を使用して、テストが実稼働環境にリリースされるのを防ぐことができます。

このアプローチのもう1つの欠点は、テストコードではなく実際のコードだけを探している場合、2つを区別するのに少し手間がかかることです。しかし、これは実際には良いことだと感じています。テストコードもコードであるという現実の痛みを感じることを余儀なくされ、独自のメンテナンスコストを追加し、他のすべてのものと同様に極めて重要な部分ですどこか側に何かがあります。

クラスごとに1つのテストクラス:

ほとんどのプログラマーにとってこれは実験的なものとはほど遠いですが、私にとってはそうです。テストするクラスごとに1つのテストクラスのみを使用して実験しています。過去には、テスト対象のクラスごとにディレクトリ全体があり、そのディレクトリ内にいくつかのクラスがありました。各テストクラスは、特定の方法でテストされるクラスをセットアップし、その後、それぞれ異なるアサーションが作成されたメソッドの束を持っていました。しかし、その後、これらのオブジェクトを取得する特定の条件に気づき始め、他のテストクラスから取得した他の条件と共通するものがありました。複製は処理するには多すぎるため、抽象化を作成して削除しました。テストコードの理解と保守が非常に困難になりました。私はこれに気付きましたが、私にとって意味のある代替案が見つかりませんでした。クラスごとに1つのテストクラスを持つだけでは、1つのテストクラス内にすべてのテストコードを含めるのに圧倒されることなく、ほぼ十分な状況をテストすることはできないように思えました。今、私はそれについて異なる視点を持っています。たとえ私が正しかったとしても、これは他のプログラマーと私自身にとって、テストの作成と保守を望んでいる巨大な抑制剤です。現在、テスト対象のクラスごとに1つのテストクラスを作成するよう強制しています。 1つのテストクラスでテストするにはあまりにも多くのことを実行する場合、テスト対象のクラスが多すぎて、複数のクラスに分割する必要があることを示すものとしてこれを見て実験しています。重複を取り除くために、私はできるだけ単純な抽象化に固執して、すべてが1つの読み取り可能なテストクラスに存在できるようにします。

[〜#〜] update [〜#〜]私はまだこのアプローチを使用しており、気に入っていますが、テストコードの量と重複の量を減らすための非常に優れた手法を見つけました。テストクラス自体の内部で再利用可能なアサーションメソッドを記述することは、そのクラスのテストメソッドによって頻繁に使用されることが重要です。内部DSL(ボブおじさんが推進しているもの、実際には内部DSLの作成を推進しているもの)と考えると、適切なタイプのアサーションメソッドを思い付くのに役立ちます。実行しようとしているテストの種類を示す単純な値を持つ文字列パラメーターを受け入れることで、このDSLの概念をさらに実際に進めることができます(実際にはDSLを作成します)。たとえば、あるとき、$ left、$ comparesAs、および$ rightパラメーターを受け入れる再利用可能なアサーションメソッドを作成しました。これにより、コードが$this->assertCmp('a', '<', 'b')のようなものを読み取るため、テストが非常に短くなり、読みやすくなりました。

正直なところ、その点を十分に強調することはできません。それはテストを書くことを持続可能なものにするための全体的な基盤です(あなたと他のプログラマーはやり続けたいことです)。それは、テストが奪う以上の価値をテストにもたらすことを可能にします。重要なのは、その正確な手法を使用する必要があるということではなく、短くて読みやすいテストを作成できるような再利用可能な抽象化を使用する必要があるということです。私は質問から話題を得ているように思えるかもしれませんが、私は本当にそうではありません。これを行わないと、最終的にはテスト対象のクラスごとに複数のテストクラスを作成する必要があるというtrapに陥ります。

1