Born Too Late

Yuya's old tech blog.

Symfony の Console の出力をテストする SpyOutput 書いた

2012-03-24 19:45:49

Symfony2 はいわゆるフルスタックな Web アプリケーションフレームワークではありますが, 各コンポーネントは疎結合になっており, それぞれは Symfony Component として再利用できる形で公開されています.
その中でも Console コンポーネントBehatComposer, また Stagehand_TestRunner など, あらゆる PHP アプリケーションに利用されています.

その Console コンポーネントを使った際に, 出力のユニットテストを行うための SpyOutput というものを書きました.
これは でいうところの Test Spy という方法を使ってテストを行います.

yuya-takeyama / console-output-spyoutput

Test Spy とは

分類についてちゃんと理解できていない可能性もあるので, 今の所の自分の理解として書きます.

Test Spy オブジェクトはモックやスタブ同様, テストのために使用する偽物のオブジェクトの一種です.
本来使うべきオブジェクトの代わりにテスト対象に差し込んで, テスト対象の動作を行ったあとで, Test Spy に問い合わせることで検証を行う, というものです.

説明だけではわかりにくいと思うので以下の PHPUnit によるテストコードをご覧ください.

Application クラスはコンストラクタで OutputInterface (Console コンポーネントに含まれるインターフェイスです) を実装したオブジェクトを受け取り, 出力はそのオブジェクトに委譲します.

本来であれば ConsoleOutput 等をセットするところを, 代わりにこの SpyOutput をセットしています.
SpyOutput は OutputInterface を実装しているので, タイプヒンティングで OutputInterface が指定されていても問題ありません.

ConsoleOutput は write() や writeln() といったメソッドに文字列を渡すことで, その文字列を標準出力に出力しますが, SpyOutput は代わりにインスタンス変数内に溜め込みます.

Application オブジェクトの実行が完了したあとに, getMessage() メソッドで SpyOutput オブジェクトに問い合わせることで, 本来であればどのような出力がされるところだったのか, を問い合わせることができます.

車輪の再発明では無いのか

作ってから気づいたのですが, Console コンポーネントには既に出力をテストするためのクラスが存在していました.

Console コンポーネントはそもそもの謳い文句が "Console eases the creation of beautiful and testable command line interfaces." となっており, いわばこれらのクラスもひとつのウリである, と考えられます.

ちょっと考えましたが, それでも SpyOutput は私にとって必要だと思いました.

これらは Console コンポーネント標準の Application や Command といった, 粒度の荒い単位でのテストを想定して作られたものであり, それより粒度の細かい単位のコンポーネントがある場合は適用することができません.

そもそも私がこれを必要としたのは, あるコマンドラインアプリケーションにおいて, その進捗状況を表示する Progress というコンポーネントを書いていて, そのテストに必要だった, というのがあります.

Progress の使用イメージは大体以下のような感じです.

これを Command という単位でテストしようとした場合, Progress とは関係の無い出力までそこに含まれてしまう可能性があります.

その可能性をできる限り排除するには, Application でも Command でもなく Progress 自体をユニットテストするのが良いでしょう.

このように, SpyOutput を使うことで, Progress だけをテストすることができました.

Progress は Symfony の Console コンポーネントの Application や Command のことは知らず, OutputInterface のみにしか依存していません. 進捗表示機能を SomeCommand 自体に持たせるのに比べると, 疎結合で, 再利用性の高い設計と言えるでしょう.

命名問題

長々と SpyOutput について説明してきましたが, 実はこのライブラリまだきちんとしたリリースができていません.
というのも, ベンダープリフィクスをどうすべきか迷っていて, 今のところ Perl っぽく X をつけて SymfonyX などとしているためです.

Symfony のためのライブラリとはいえ, 勝手に Symfony というベンダープリフィクスを使うのは多分ダメだろうし, そうなると何というプリフィクスを付けるべきでしょう?
どなたか教えてください.

(Symfony 本体に Pull Request 投げて取り入れてもらえると楽なんだけど...)

まとめ

この記事では Symfony の Console コンポーネントの出力をテストするライブラリ SpyOutput と, Test Spy というテスト手法について説明しました.
命名問題が解決したら, Packagist 辺りに公開するつもりです.