PHPerのための「PHPUnitの新バージョンを語り合う」PHP TechCafe ============================================ * PHPUnitの概要(廣部) * 機能紹介 * メンテナーについて * 簡単な導入手順(浅野) * 新プロジェクトに入れる場合 * 既存プロジェクトに入れる場合 * 簡単な使い方(森谷) * 前の資料を要約して短く * リリース情報 * 頻度 * 最近3バージョンぐらいの変更点 * PHPUnit8(加納) * PHPUnit9(久山) * PHPUnit10(堀川) # PHPUnitの概要(廣部) ## 機能紹介 [PHPUnitのトップページ](https://phpunit.de/)には以下のように書かれている PHPUnit は、プログラマ向けの PHP 用テストフレームワークです。ユニットテストフレームワークの xUnit アーキテクチャの一種です。 (原文) PHPUnit is a programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. ### できること [ドキュメント](https://docs.phpunit.de/en/10.3/index.html) - 返り値のアサーション - 期待する例外のアサーション - データプロバイダー - 配列の内容を利用してテスト実行してくれる - 出力結果のアサーション - echoやprintなどで出力された結果のアサートができる - 不完全なテストのマーク - テストのスキップ - 属性を利用したテストのスキップ - 依存関係のテスト - 失敗理由の出力 - テストスタブ、モックオブヘクトの作成 ユニットテストフレームワークで実行したいことは一通りできます ## メンテナーについて Sebastian Bergmann # 簡単な導入手順(浅野) 下記のどちらの場合でも同じ方法で導入可能 - 新プロジェクトに入れる場合 - 既存プロジェクトに入れる場合 ## Composerを使ってPHPUnitをインストールする Composerを利用することで簡単にPHPUnitをインストールすることができます。 下記コマンドを実行するだけでインストールが完了する ``` composer require --dev phpunit/phpunit ``` ただし、PHPUnit をインストールするには PHP Archive (PHAR) を使うのが推奨されているみたいです。 https://docs.phpunit.de/en/10.3/installation.html#php-archive-phar PHPUnitは依存関係が多い? ### 参考サイト https://docs.phpunit.de/en/10.3/installation.html#installing-phpunit-with-composer ## Hello World ### 準備 テスト対象のクラス`Greeter`を作成します。 ``` <?php declare(strict_types=1); final class Greeter { public function greet(string $name): string { return 'Hello, ' . $name . '!'; } } ``` ### 実際のテストコード 下記がテストコードになります。 ``` <?php declare(strict_types=1); use PHPUnit\Framework\TestCase; final class GreeterTest extends TestCase { public function testGreetsWithName(): void { $greeter = new Greeter; $greeting = $greeter->greet('Alice'); $this->assertSame('Hello, Alice!', $greeting); } } ``` `Greeter`クラスの`greet`メソッドのテストを行っています。 `greet`メソッドの返り値が`'Hello, Alice!'`と同じであることをテストしています。 ### 実行結果 下記がPHPUnitの実行結果になります。 ``` ./tools/phpunit tests/GreeterTest.php PHPUnit 10.3.0 by Sebastian Bergmann and contributors. Runtime: PHP 8.2.10 . 1 / 1 (100%) Time: 00:00, Memory: 22.46 MB OK (1 test, 1 assertion) ``` 1/1の結果で100%成功しているとなっています。 ### 参考サイト https://docs.phpunit.de/en/10.3/writing-tests-for-phpunit.html#asserting-return-values # 簡単な使い方 ### テスト実行前後の処理 メソッドの呼び出しの度、事前処理を作るには`setUp()`メソッド、事後処理を加えるには`tearDown()`メソッドを定義する ##### setUp()メソッド クラスインスタンスの初期化などを必要なテストクラス共通の処理を分離させておきたいときに作成 ```php class テストクラス名 extends TestCase { protected function setUp(): void { // テストメソッド実行前に行いたい処理 } } ``` ##### tearDown()メソッド リソースの解放(ファイルのクローズやDBとの接続の終了)など共通の処理を分離させたいときに作成 ```php protected function tearDown(): void { // テストメソッド終了後に行いたい処理 } ``` ### アサーション 値を比較、検査して想定通りの値になっているかを確認するためのメソッド | メソッド | 役割 | 例文 | | ------------------------------- | ---------------------------------------- | -------------------------------------- | | assertSame(\$expected, \$actual) | \$expectedと\$actualが同じ型・同じ値かを検証する 配列は並び順が同じであるかも検証する | \$this->assertSame(100, 100); | | assertEquals(\$expected, \$actual) | \$expectedと\$actualが同じ値かを検証する 配列の並び順については検証しない | \$this->assertEquals(100, "100"); | | assertTrue(\$condition) | \$conditionがtrueかどうかを検証する | \$this->assertTrue(true); | | assertFalse(\$condition) | \$conditionがfalseかどうかを検証する | \$this->assertFalse(false); | | assertNull(\$variable) | \$variableがnullかどうかを検証する | \$this->assertNull(null); | | assertNotNull(\$variable) | \$variableがnullではないかを検証する | \$this->assertNotNull(null); | | assertCount(\$expectedCount, \$haystack) | \$haystack の要素数が \$expectedCount と等しいかを検証する | \$this->assertCount(1, ['aaa']); | | assertGreaterThan(\$expected, \$actual) | \$actual の値が \$expected の値より大きいかを検証する | \$this->assertGreaterThan(10, 100); | ### 例外のテスト 例外が発生したかどうかを判定する場合は`expectException()`メソッドを使用する ```php <?php use PHPUnit\Framework\TestCase; class テストクラス名 extends TestCase { public function テストメソッド() { $this->expectException(発生する例外クラス名::class); // 例外が発生する処理 } } ``` ### データプロバイダメソッド テストメソッドへの引数をまとめて記載することができるメソッドを作成することができる データプロバイダに指定するメソッドには、アノテーション @dataProvider を指定し、配列や反復が可能な値を返すようにする必要がある ```php <?php use PHPUnit\Framework\TestCase; class DataTest extends TestCase { /** * @dataProvider additionProvider */ public function testAdd($a, $b, $expected) { $this->assertEquals($expected, $a + $b); } public function additionProvider() { return [ [0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 3] ]; } } ?> ``` ### アノテーション 各テストメソッドに対するメタ情報 アノテーション自体は PHPDoc にも利用することがあるが、PHPUnit では各テストメソッドの依存関係を定義することなどが可能 ##### @depends アノテーションが付与されたテストメソッド側で依存しているテストメソッドの戻り値を引数として受け取れる 記述方法は以下の通り ```php @depends + "テストメソッド名" ``` 以下は PHPUnit の公式マニュアルで紹介されているサンプルコード ```php <?php declare(strict_types=1); use PHPUnit\Framework\TestCase; final class StackTest extends TestCase { public function testEmpty(): array { $stack = []; $this->assertEmpty($stack); return $stack; } /** * @depends testEmpty */ public function testPush(array $stack): array { array_push($stack, 'foo'); $this->assertSame('foo', $stack[count($stack)-1]); $this->assertNotEmpty($stack); return $stack; } /** * @depends testPush */ public function testPop(array $stack): void { $this->assertSame('foo', array_pop($stack)); $this->assertEmpty($stack); } } ``` ### モック テスト時に、実際のオブジェクトの動作をシュミレートしてくれる模造品オブジェクトのこと 依存するオブジェクトが何らかの理由でテスト時に利用できないときに使用する たとえば、 - まだ実装されていない - APIを実行するオブジェクト - オブジェクトの依存関係をテストで使えない サンプルコード: https://phpunit.readthedocs.io/ja/latest/test-doubles.html#test-doubles-mock-objects # PHPUnit9 * [PHPUnit9](https://github.com/sebastianbergmann/phpunit/tree/9.6.12) * [PHPUnit 9時代のTest Doubleの作り方](https://speakerdeck.com/hgsgtk/deep-dive-into-mockbuilder-of-phpunit-9) ## PHPUnit9.0 ### Added * [#3797](https://github.com/sebastianbergmann/phpunit/pull/3797): Add support for multiple `--whitelist` options * コードカバレッジレポートに出力する対象とするファイルを複数指定できるようにする * これまでは1つのファイルしか設定できなかった * XMLの設定ファイルではこれまでも複数指定することができた * 開発時などに細かくカバレッジを確認したい場合、XMLの設定ファイルを修正するのは手間である * 開発時に修正したXMLの設定ファイルをリモートリポジトリにPUSHしてしまう事故も発生していた * `sebastianbergmann` 氏は複数のXMLファイルを受け付けれるように今後修正する予定であることを示したが上記については同意した模様で取り入れられた ### Removed * [#3334](https://github.com/sebastianbergmann/phpunit/issues/3334): Drop support for PHP 7.2 * PHP7.2 のサポート終了 * [#3339](https://github.com/sebastianbergmann/phpunit/issues/3339): Remove assertions (and helper methods) that operate on (non-public) attributes * 属性を操作する各種アサーションとヘルパーメソッドを削除 * テストはプライベートな実装の詳細に依存すべきではない。 * このメソッドがあることで悪いテスト手法が蔓延することを防ぎたかった。 * (テスト設計の話になるため、正直あまりわかっていないです・・・) ## PHPUnit9.1 ### Added * [#4100](https://github.com/sebastianbergmann/phpunit/issues/4100): Implement `failOnIncomplete` and `failOnSkipped` configuration options as well as `--fail-on-incomplete` and `--fail-on-skipped` commandline options * テストがすべて成功しているが、不完全であったりスキップされたテストがある場合にエラーとする設定が追加された * テストが成功しているが不完全やスキップってどういう状況・・・? * [#4130](https://github.com/sebastianbergmann/phpunit/pull/4130): Canonicalize JSON values in failure message * JSONで出力されたエラーメッセージをJSONの形式で正しく出力する * 前後比較できていないのですが、[実装](https://github.com/sebastianbergmann/phpunit/pull/4130/files)を見たところ、これまではエラーメッセージのValue値などがJSONになっていなかったものと思われます。 * [#4136](https://github.com/sebastianbergmann/phpunit/pull/4136): Allow loading PHPUnit extensions via command-line options * CLI経由で PHPUnit の拡張を追加できるようにするオプション `--extensions ` が追加になった * [paraunit](https://github.com/facile-it/paraunit/) というPHPUnitのユニットテストを並列実行するためのツール開発者からの質問 * これまではXMLファイルを直接修正する必要があったが、設定ファイルを汚してしまうことのリスクなどからCLI経由でインポートできるようにしたいという要望があった * このPR でも触れられていますが、[Codecove](https://qiita.com/nasum/items/aff9bf09d49b136bbf94) 普通に便利そうですね。ユニットテストのカバレッジ監視にはすごく役立ちそうです。 ### Changed * [#4095](https://github.com/sebastianbergmann/phpunit/pull/4095): Improve performance of `StringContains` constraint * StringContains の高速化 * これまではいかなる文字の Contains もマルチバイト用の関数 [mb_stripos](https://www.php.net/manual/ja/function.mb-stripos.php) を利用していたが、マルチバイト文字の比較を行わない場合は [strpos](https://www.php.net/manual/ja/function.strpos) でよいはず、という指摘。 ## PHPUnit 9.2 ### Added * [#4224](https://github.com/sebastianbergmann/phpunit/issues/4224): Support for Union Types for test double code generation * テストダブルのための Union 型をサポート対象とした ## [9.3.0] - 2020-08-07 ### Added * [#3936](https://github.com/sebastianbergmann/phpunit/pull/3936): Support using `@depends` to depend on classes * `@depends` による依存の対象をクラス単位にできるようになった? * [#4325](https://github.com/sebastianbergmann/phpunit/issues/4325): Support PHP 8 * PHP8.0 をサポート対象に追加 ## [9.4.0] - 2020-10-02 ### Added * [#4462](https://github.com/sebastianbergmann/phpunit/pull/4462): Support for Cobertura XML report format * カバレッジレポートの出力オプション Cobertura をXMLで出力できるようになった。 * これにより、Cobertura のXMLを受けつけている外部ツールと連携しやすくなった模様 * [GitLabの例](https://gitlab-docs.creationline.com/ee/user/project/merge_requests/test_coverage_visualization.html) * [#4467](https://github.com/sebastianbergmann/phpunit/issues/4467): Convenient custom comparison of objects * オブジェクト用のアサーションメソッドが追加になった ## [9.5.0] ### Changed * [#4490](https://github.com/sebastianbergmann/phpunit/issues/4490): Emit Error instead of Warning when test case class cannot be instantiated * [#4491](https://github.com/sebastianbergmann/phpunit/issues/4491): Emit Error instead of Warning when data provider does not work correctly * [#4492](https://github.com/sebastianbergmann/phpunit/issues/4492): Emit Error instead of Warning when test double configuration is invalid * [#4493](https://github.com/sebastianbergmann/phpunit/issues/4493): Emit error when (configured) test directory does not exist * 各種、Warning レベルのものをErrorに引き上げる対応 * うっかりスルーしないようになった ## 9.6 は目立った新機能は無し おそらく、この時期に PHPUnit10 がリリースされたため? * 9.6 * 2023-02-03 * 10.0 * 2023-02-03 # PHPUnit10 https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md#1000---2023-02-03 ## Added - テストイベントシステムの追加 - https://docs.phpunit.de/en/10.3/extending-phpunit.html#extending-the-test-runner 1. 関連するイベントのSubscriberを作成 2. Subscriberを設定するExtensionを作成 3. phpunit.xmlにExtensionを追加 - PHP8で実装されたアトリビュートの対応 - assertStringEqualsStringIgnoringLineEndings() - 行末文字を無視した文字列比較アサーション - OSの違いやGitの設定の違いによる行末文字の違いを無視して比較できる - assertIsList() - 配列のキーが0始まりの連番であるかを検証するアサーション - HTMLコードカバレッジレポートで使用される色を設定可能に - TestDoxのenumサポート - --display-○○オプションの追加 - デフォルトで表示されなくなったdeprecationやnoticeを表示させる - --no-○○オプションの追加 - 経過や結果などを表示しないようにできる ## Changed - キャッシュ - `<phpunit>` ルート要素に cacheDirectory 属性を導入 - PHPUnit テストランナーが使用するキャッシュディレクトリやキャッシュファイルを単一のルートディレクトリで設定できるように。 - PHPUnitではfieldをアトリビュートとずっと呼んでいるのでその呼称をプロパティに変更 - PHP8 で追加されたアトリビュートの影響 - data-providerメソッドの定義が厳格に - publicでないとdeprecated - staticでないとdeprecated - 引数を要求するとdeprecated - テストコードでE_DEPRECATED,E_NOTICE,E_STRICT,E_WARNINGが発生してもテストの実行が継続されるように - bootstrapスクリプトのスコープでグローバルな変数を、テストランナーのスコープでグローバル変数に昇格させなくなる - bootstrapでglobal変数を一覧定義などしている場合は動かなくなる - --stop-on-○○を使用した場合のみ、テストの実行が中断されるようになる - 今までと揃えたいのなら--step-on-defectを使用する ## Removed - 文法誤りや判断のしづらいメソッド名の修正 - 例:assertNotIsReadable() -> assertIsNotReadable() - assertEqualXMLStructure() - XML構造の検証に使用するアサート - deprecatedになり将来的に削除される - これ以外にもXML関係は削除されたものがちょこちょこある(--textdcx--xmlオプションなど) - 差別的用語の削除 - 例:Blacklist, --whitelist - 削除されたが代替手段の無いメソッド - at($index) - モックで使用 - 評価対象メソッドが$index回目に実行された際にマッチするオブジェクトを返す - withConsecutive(...$arguments) - テスト対象の呼び出しに合わせて、引数の配列を渡す - 個々の配列は制約リストになっており、モック対象メソッドのそれぞれの引数に対応する - class・オブジェクトのプロパティに対するアサーションの削除 - 例:assertObjectHasAttribute() - 修正例 ```php - $this->assertObjectHasAttribute('c', $sut->a->b); + $this->assertIsObject($sut->a->b); + $this->assertTrue(property_exists($sut->a->b, 'c')); ``` - expect○○()が一部削除 - 例:expectNotice,expectError - expectExceptionで代用は可能だが... - version10から特定のテストの結果やテスト自体の問題が発生しても例外を投げなくなったのでそもそも検知できない - TestListenerやTestHookの削除 - 新しいテストイベントシステムが追加された影響 - Testcaseの一部プロパティをアトリビュートへ - 例:PHPUnit\Framework\TestCase::$backupGlobalsExcludeList → #[ExcludeGlobalVariableFromBackup('variable')] - オプション削除 - --repeat,--debug,--verbose辺りは使っていた人もそこそこいるのでは - PHPバージョンサポート - 7.3,7.4,8.0を削除 * どのバージョンでどのアサートが増えて消えたか