あまりにも有名な PHP のユニットテストツール。CLI で叩いて実行、カバレッジの HTML 出力とかもやってくれるやつ。単体でも利用できるが composer でインストールして CakePHP など FW とセットで利用されるケースや Phing のような PHP ビルドツールとセットで利用するケースも多い。
- 単体テストは理想論として 全てのクラスの全ての
public
メソッドの全ての分岐 を埋めるように作成したい - 同じメソッドの違う振る舞いをテストする場合はテストメソッドを分ける
private
メソッドの複雑なテストをするのは議論が分かれるところらしい
- まずはコードカバレッジを出力しアプリケーションのクラス、メソッド数と テスト網羅率 をチェック
- テストで埋めるべきモデル層やプレゼンテーションの層コンポーネント系から埋めていくイメージ
- 単体テストはあくまでも「メソッド」のテストのため 実際に依存物を連携させないでモック化する
- PHPUnitのモックオブジェクトの使い方を仕組みから理解する
- テストメソッド間の依存は
@depends
で明示しインスタンスを再利用する- PHPUnitでテストメソッド間の依存性を表現する
@depends
は実行順序を制御するものではないため DB 状態などは引き継がれない点に注意
- 各テストメソッド毎に
setUp()
tearDown()
が走るため DB は常に Fixture の状態になる - テストデータを使いまわしたい場合は以下頑張る
- Fixture を適切に用意する
- プロパティにデータをセットしておく
setUp()
などで毎回登録を走らせる
- 環境に依存するテストや FW のスケルトンコードなど不要なものはテストそのものを省略すべき
- 不完全なテスト・テストの省略
- テストカバレッジ100%を追求しても品質は高くならない理由と推奨されるカバレッジの目標値について
- カバレッジが高くても「バグを検出できるテストコード」とは限らない
- 「バグを検出できるテストコード」を頑張って実装したら 100 % に届くのに恐ろしく時間がかかる
- Google 様は命令形網羅のため 85 % を目標値にしている ( あくまでも努力目標 )
- 定数は当然のことながら 単体テスト時に動的に変更できない ので Testable でなくなる
- PHP の Runkit 拡張で定数を再定義したり、定数ファイルをテスト用に分けたりしなきゃいけなくなる
- 引数のデフォルト値として定数をセットして メソッド自体は定数に依存しない設計にすべき
use PHPUnit\Framework\TestCase;
class StackTest extends TestCase
{
/**
* Test: Push and pop by array.
* @test
*/
// @test を省略する場合メソッド名は test* にする必要あり
public function pushAndPop()
{
$stack = [];
$this->assertSame(0, count($stack));
array_push($stack, 'foo');
$this->assertSame('foo', $stack[count($stack)-1]);
$this->assertSame(1, count($stack));
// var_dump() とかしたくなったやつを assertion で片づけていく
$this->assertSame('foo', array_pop($stack));
$this->assertSame(0, count($stack));
}
}
基本的に設定ファイル phpunit.xml
とセットで利用する。CakePHP なんかを Composer でインストールした場合は、この設定ファイルのひな型 phpunit.xml.dist
生成や composer.json
の scripts の設定までしてくれるので何も考えなくていい。
$ phpunit
# Options
# --bootstrap <file> 実行前に参照する require などが書かれた PHP ファイル
# --configuration <file> 省略時は現 DIR の phpunit.xml ( .dist ) を参照
# --colors=<flag> 色の出力 ( flag は never, auto, always )
# --verbose 詳細情報の出力
# --debug デバッグ情報の出力
# --coverage-html <dir> コードカバレッジを HTML 形式で dir へ出力
# --filter <pattern> 正規表現ライクなパターンで実行するテストをフィルタリング
$ composer test
# composer.json は例えばこんな風に
#
# "scripts": {
# "test": "vendor/bin/phpunit -v --debug --coverage-html tests/coverage"
# }
出力 | テスト結果 |
---|---|
. | テスト成功 |
F | テスト失敗 |
E | 危険マーク |
S | スキップ |
I | 未実装 |
// mixed is ...
$this->assertNull($var); // $varがNULLである
$this->assertEquals($val1, $val2); // $val1が$val2と等しい
$this->assertSame($val1, $val2); // $val1と$val2が型も含めて等しい
$this->assertInternalType($type, $val); // $valの型名が$typeである - @link https://goo.gl/X71MJ2
// number is ...
$this->assertGreaterThan($expect, $var); // $expect < $var が成立する
$this->assertGreaterThanOrEqual($expect, $var); // $expect <= $var が成立する
$this->assertLessThan($expect, $var); // $expect > $var が成立する
$this->assertLessThanOrEqual($expect, $var); // $expect >= $var が成立する
// string is ...
$this->assertJsonStringEqualsJsonString($str1, $str2); // $str1と$str2がjsonとして等しい
$this->assertRegExp($ptn, $str); // $strが正規表現$ptnにマッチする
// boolean is ...
$this->assertTrue($var); // $varがTRUEである
$this->assertFalse($var); // $varがFALSEである
// array is ...
$this->assertArrayHasKey($key, $array); // 配列$arrayにキー$keyが存在する
$this->assertContains($val, $array); // 配列$arrayに値$valが存在する
$this->assertContainsOnly($type, $array); // 配列$arrayの値の型がすべて$typeである
$this->assertCount($count, $array); // 配列$arrayの値の数が$countである
$this->assertEmpty($array); // 配列$arrayが空である
// object/class is ...
$this->assertObjectHasAttribute($attr, $object); // オブジェクト$objectにプロパティ変数$attrが存在する
$this->assertClassHasAttribute($attr, $class); // クラス名$classにプロパティ変数$attrが存在する
$this->assertClassHasStaticAttribute($attr, $class); // クラス名$classに静的プロパティ変数$attrが存在する
$this->assertInstanceOf($class, $instance); // $instanceがクラス名$classのインスタンスである
// file is ...
$this->assertFileExists($file); // $fileが存在する
$this->assertFileEquals($file1, $file2); // $file1と$file2の内容が等しい
$this->assertJsonFileEqualsJsonFile($file1, $file2); // $file1と$file2の内容がjsonとして等しい
$this->assertJsonStringEqualsJsonFile($file1, $json); // $file1の内容と$jsonがjsonとして等しい
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
colors="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/bootstrap.php"
>
<php>
<ini name="memory_limit" value="-1"/>
<ini name="apc.enable_cli" value="1"/>
</php>
<!-- Add any additional test suites you want to run here -->
<testsuites>
<testsuite name="app">
<directory>./tests/TestCase</directory>
</testsuite>
<!-- Add plugin test suites here. -->
</testsuites>
<!-- Setup a listener for fixtures -->
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector"
file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager" />
</arguments>
</listener>
</listeners>
<!-- Ignore vendor tests in code coverage reports -->
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
<directory suffix=".php">./plugins/*/src/</directory>
</whitelist>
</filter>
</phpunit>