動機
PHPのテストフレームワークといえば(たぶん)PHPUnitが一番に挙げられると思いますが、歴史が長いだけにテストの書き方も古風というか今風ではないというモヤモヤを感じる今日この頃。
他のテストフレームワークで台頭しているのはPHPSpecとかBehatあたりだと思いますが、普段jsでmochaとchai(最近はpower-assert)を使っている身からするとどちらもしっくりきませんでした。
そんなわけでjsのモダンなテストフレームワークに近い書き方ができるPHPテストフレームワークを探してみたところ、Peridotというものを見つけたのですが、これが思いの外使い勝手が良かったので、紹介と共に普及活動としてスターターキットやGulpプラグインを作ってみました。紹介はのちほど。
ちなみにPHP5.4~対応なのでレガシーなシステムでは導入できません。ご注意を。
Peridotの紹介
まずはサンプルを見てみましょう。
describe('ArrayObject', function() {
beforeEach(function() {
$this->arrayObject = new ArrayObject(['one', 'two', 'three']);
});
describe('->count()', function() {
it("should return the number of items", function() {
$count = $this->arrayObject->count();
assert($count === 3, "expected 3");
});
});
});
見るからに今風なBDDスタイルですね。パッと見でjsと勘違いしてしまうんじゃないかと思うほどです(言い過ぎ)。
このようにjsとphpでほぼ同じようなテストの書き方ができるというのがイチオシな点です。
もちろんテストの書き方だけでなく、Peridotには他にも面白い機能が実装されています。
テストの保留/解除
まだ未実装で通らないテストを一時的に保留させる方法です。
xdescribe("A pending suite", function() {
xcontext("when using a pending context", function() {
xit("should have a pending spec", function() {
});
});
});
describe
やcontext
、it
の頭にx
が付けられています。このように頭にx
が付いたテストは実行されず、pending扱いされます。pendingとなったテストの数も表示されます。
コメントアウトして一時的テストを保留させると、コメントアウトしたことを忘れてしまって結局テストされず、なんてことになりかねませんが、このように頭のx
のトグルだけでテストの保留を切り替えられ、なおかつそれがテスト時に表示されるのはなかなか便利な機能ではないでしょうか。
アサーションライブラリ「Leo」
Peridotと同じ作者さんが公開しているLeoというアサーションライブラリがあります(Peridotとは別でインストールが必要)。こちらを組み合わせるとよりモダン感溢れるテストを書けるようになります。Expect、Assertインターフェイスを実装しています。
jsでいえばchaiに近い感覚で使用することができます。
describe('test hoge', function() {
it('should have 3 items', function() {
expect([1,2,3])->to->have->length(3);
});
});
use Peridot\Leo\Interfaces\Assert;
describe('test hoge', function() {
it('should have 3 items', function() {
$assert = new Assert();
$assert->equal(count([1,2,3]), 3);
});
});
Assertインターフェイスの場合はテストの度にnew
するか、$this->assert
みたいな感じで保持しておく必要があるのでちょっと面倒ですが、ラッパー関数を作ればそれほどストレスはたまりません。
use Peridot\Leo\Interfaces\Assert;
function ass() { return new Assert(); }
describe('test hoge', function() {
it('should have 3 items', function() {
ass()->equal(count([1,2,3]), 3);
});
});
このようにPeridotとLeoを組み合わせることによってjsでいうmochaとchaiを使った時とほぼ同じ感覚でPHPのテストを書くことができます。
各種カスタマイズ
Peridot自体はPHPUnitほど機能が豊富なわけではありません。テスト結果の表示形式(reporter)も1種類しかありません。しかし設定ファイルやプラグインによる拡張で、かなりのカスタマイズができるようになっています。
設定ファイル:peridot.php
Peridotによるテスト実行時にはデフォルトでカレントディレクトリにある__peridot.php__が読み込まれます(あれば)。コマンドラインオプションで設定ファイルのパスを指定することもできます。
設定ファイルは以下のように書きます。
use Evenement\EventEmitterInterface;
use Peridot\Core\Test;
return function(EventEmitterInterface $emitter) {
$emitter->on('test.failed', function (Test $test) {
//log the failure?
//abort abort abort?
//let your loved ones know about your shortcomings?
});
}
EventEmitterInterface
を引数とする関数を返すのが設定ファイルのルールとなっています。
上の例ではtest.failed
、つまりテストが失敗したタイミングでPeridot本体から呼び出される関数を$emitter->on()
で登録しています。
イベントベースで処理を書くのもなんかjsっぽい感じがしますね。
で、拡張っつってもイベント発生時に何すればいいのよ?ってことですが、
たとえばコードカバレッジ機能を組み込みたい場合は、peridot.start
時にPHP_CodeCoverage
をnew
してperidot.reporters
でカバレッジ結果を出力する、みたいな。
他にもsuite.start
やtest.passed
などいろいろなイベントが用意されています(イベントによって渡される引数の種類は異なるのでご注意を)。詳しくは公式ドキュメントをご覧ください。
独自コマンドラインオプションの追加
さらにPeridotでは独自のコマンドラインオプションを登録することができます。
デフォルトでは以下のような感じですが、
$ peridot --help
Usage:
peridot [options] [files]
Options:
--grep (-g) Run tests matching <pattern> (default: *.spec.php)
--no-colors (-C) Disable output colors
--reporter (-r) Select which reporter to use (default: spec)
--bail (-b) Stop on failure
--configuration (-c) A php file containing peridot configuration
--reporters List all available reporters
--version (-V) Display the Peridot version number
--help (-h) Display this help message.
コマンドラインオプションを追加することによってこんな感じにすることができます。コードカバレッジ関係の拡張をした時の例です。
$ peridot --help
Usage:
peridot [options] [files]
Options:
--grep (-g) Run tests matching <pattern> (default: *.spec.php)
--no-colors (-C) Disable output colors
--reporter (-r) Select which reporter to use (default: spec)
--bail (-b) Stop on failure
--configuration (-c) A php file containing peridot configuration
--reporters List all available reporters
--version (-V) Display the Peridot version number
--help (-h) Display this help message.
--coverage-html Code coverage(HTML) report directory
--coverage-xml Code coverage(XML) report directory
--coverage-clover Code coverage(Clover) report file
--coverage-php Code coverage(PHP) report file
--coverage-crap4j Code coverage(Crap4j) report file
--coverage-text Code coverage(Text) report file
--coverage-blacklist (-B) Blacklist file/dir for Code coverage (multiple values allowed)
--coverage-whitelist (-W) Whitelist file/dir for Code coverage (multiple values allowed)
コマンドラインオプションの追加は以下のように行います。
$emitter->on('peridot.start', function (Environment $env, Application $app) {
$env->getDefinition()->option(
'hoge', // --hogeオプションとして登録される
'H', // --hoge = -Hとなる
InputOption::VALUE_REQUIRED, // --hoge=fugaみたいに値の指定が必須
'example option hoge' // --help時に表示されるオプション説明
);
}
で、追加した--hoge
オプションが指定されてPeridotが起動したらそのオプションを受け取ってゴニョゴニョするという流れです。コマンドラインオプションの受け取り方はこんな感じです。
$emitter->on('peridot.execute', function (InputInterface $input, OutputInterface $output) {
$hoge = $input->getOption('hoge');
}
しかし、こんな感じにコマンドラインオプションを登録してコマンドラインオプションを受け取って、その値を元に云々・・・というようなことを書いていくとあっという間に設定ファイルが肥大化してしまいます。
カスタマイズのプラグイン化
そこで、このように大量の記述が必要な設定はプラグイン(外部ライブラリ)化して、設定ファイルではプラグインをロードする形にするとスッキリさを保てます。
require('plugin-hoge.php');
use Evenement\EventEmitterInterface;
use MyPlugin\Hoge;
return function(EventEmitterInterface $emitter) {
new Hoge($emitter); // この中で各種イベント処理やコマンドラインオプションの登録など
};
プラグインの書き方ですが、ベースとなるクラスもないのでこれといって決まりはなさそうですが、後述する公式のプラグインのソースをパク参考にすると良いと思います。
その他
ここまで紹介した以外にもScopeやDSLのカスタマイズ・拡張といったことが可能なようですが、この辺りはちゃんと調べていないので紹介のみにとどめておきます。
公式プラグイン
というかまだ知名度が低いフレームワークなので有志のプラグインというものはほとんどありません。
https://github.com/peridot-phpで見つけたよさげなプラグインを簡単に紹介します。
peridot-dot-reporter
1つのテスト結果を1ドットで表示するrepoterを使用できるようにするプラグインです(画像は公式より拝借)。
peridot-list-reporter
1つのテスト結果を1行で表示するrepoterを使用できるようにするプラグインです(画像は公式より拝借)。
peridot-code-coverage-reporters
コードカバレッジの結果を出力するreporterを使用できるようにするプラグインです。とはいえ自分はこのプラグインを発見する前に自前でコードカバレッジプラグインを作ってしまっていたので使ったことはありません。
peridot-yo-plugin
一昔前に流行ったYoでYoできるプラグインのようです。自分はYoをYoく知らないのでこれが便利なものなのかネタなのかは分かりません。
peridot-watcher-plugin
最近のビルドツールでは当たり前となっているファイル変更監視機能ですが、Peridotの場合は単体でこの機能をプラグインで導入することができます(画像は公式より拝借)。
leo-http-foundation
PeridotではなくアサーションライブラリのLeoを拡張して、http関連のアサーションメソッドを追加するプラグインです。
peridot-dsl-example
こちらはプラグインではありませんが、DSLのカスタマイズのサンプルです。DSLを拡張してこんなことができるよー的なやつです。
peridot-scope-example
同じくScopeのカスタマイズのサンプルです。
普及活動1: この記事
やっぱり日本語の記事があると入りやすさが違うよね!ということでこの記事をしたためております。
普及活動2: スターターキット
Peridotによるテストを簡単に始めることができるスタータキットを作成しました。
src
ディレクトリにテストしたいモジュールが入っていて、specs
ディレクトリ内のテストスクリプトを実行するという流れです。インストールや実行の仕方はREADME.mdをご覧ください。
なお、peridot-easy
ディレクトリには自作のプラグインなどが入っています。簡単にカスタマイズ内容を紹介します。
コードカバレッジ機能
コマンドラインオプションでコードカバレッジの指定やブラックリスト/ホワイトリストの登録ができるようになります。
$ vendor/bin/peridot --coverage-html report -B peridot-easy -B peridot.php -B foo -B bar.php
テスト結果を顔文字で表示するreporter追加
ふざけてません(真顔)。いや何というかデフォルトのreporterはWindows7環境でテスト結果のシンボルが文字化けするのが気に入らなかったのでASCII文字だけでテスト結果を表現しようとしたらこうなってました。
$ peridot -r face
で、このふざけた顔文字reporterを使用できます。
あと、このreporterはエラー時のスタックトレースを排出しません。デフォルトのreporterだとスタックトレースが多すぎてエラー情報がスクロールアウトしてしまうことと、排出されるスタックトレースのほとんどがPeridot内部に関するものなのでテスト時には邪魔なケースの方が多かったというのが理由です。
普及活動3: Gulpプラグイン
Peridot自体にWatcherプラグインがあるので別にGulpに組み込まなくてもいいんですが、オプションの指定とかも楽だし、gulpfile.jsで一元管理したい欲求とかが襲ってきたので作りました。
$ npm install --save-dev gulp-peridot
でインストールできます。
このプラグインには上で紹介したスターターキットの拡張機能が組み込まれているので、簡単にコードカバレッジ機能やふざけたお茶目な顔文字reporterを使用することができます。
こちらもREADME.mdやexampleなどを参考にしてください。
ちなみにこのプラグインはgulp-phpunitの使い方やオプションを可能な限り踏襲しているので、gulp-phpunitからの乗り換えも簡単にできるようになっています。
まとめ
自由にカスタマイズできたりプラグインがいろいろあるとはいえ、それでもPHPUnitがデフォルトで提供している機能(アノテーションとかモックとか)には及ばないとは思います。しかし、そもそも自分の場合はPHPUnitの豊富な機能をほとんど使ってなかったので、PeridotとLeoのモダンなテスト記法と、足りない部分をプラグインやカスタマイズで補強していくスタイルが気に入りました。
しかしマイナー故に開発停滞→自然消滅みたいなことがあっては勿体ないので、こうして普及活動をしてみました。この記事を見てPeridotキニナルという方が増えれば我が生涯に一片の悔いなしです(言い過ぎ)。