以前紹介した MapReduce フレームワーク MyMR の開発を少し進めました.
パッケージとしてのリリースはまだですが.

この記事では変更点等について紹介します.

MyMR の仕組みなどについては前回の記事を参照してください.

ビルダーによる Mapper/Reducer の定義

元々はクラスにメソッドを定義する形で行っていた Mapper/Reducer の定義を, ビルダーを使って定義できるようにしました.

この方法を使った場合, Mapper も Reducer も無名関数だけで定義できるようになったので, よりカジュアルになったと言えるでしょう.

また, クラスベースの定義では, 入出力のテーブルをコマンドラインオプションで指定していましたが, こちらではビルダーで指定できるようになりました.
なので設定ファイルの別出しすることもできるようになっています.

また, コマンドラインで指定した場合はそちらが優先されるので, テスト時はテスト用のデータベースを使う, ということもできます.

無名関数以外でも, call_user_func_array() で実行できるもの (PHP 5.4 で言うところの callable) であれば何でもいいので, インスタンスメソッドなどを Mapper として定義することもできます.

簡単なものは無名関数でサクッと定義してい, 複雑なものはユニットテストされたクラスのメソッドで定義する, といった使い分けができるようになっています.

クラスベースの定義も一応残していますが, ビルダーの方が圧倒的に使いやすいように思えるので, そのうち消すかもしれません.

コマンド名の変更

以前は mymr execute コマンドでクラスベースの MapReduce 定義を実行していましたが, ビルダーによる定義の追加に伴い mymr class コマンドに変更しました.

Emitter の切り出し

Mapper はもともと \MyMR \Base クラスの emit メソッドを $this->emit($key, $value) として読んでいましたが, 無名関数で同様のことをやるには PHP 5.4 以降の Closure::bindTo() を使用する必要が出てきてしまうため, Emitter クラスを別に作り, Mapper に引数として渡すようにしました.

これにより, Mapper のテスト時には Emitter をモックオブジェクトや Test Spy オブジェクトに差し替えてテストする, ということもできるようになりました.

進捗の表示

元々は実行しても無言で終了していましたが, Map と Reduce それぞれで進捗を表示するようにしました.

進捗の表示

進捗の表示

今回 \MyMR \Progress という形で実装しましたが, これ自体の MyMR との結合は緩く, Symfony の Console コンポーネントの OutputInterface にしか依存していないので, 別で公開できればと思っています.

まとめ

遊びで作り始めたものですが, 少しずつ様になって来ました.
まだまだ開発が安定しないので, インターフェイス等は大幅に変わる可能性があると思いますが, 引き続き開発を続けて行きたいと思います.

MyMR については, 3/27 の第58回PHP勉強会@東京で発表する予定なので, 後日スライドを公開する予定です. (今からつくる)

あと, MySQL Casual Talks Vol. 3 の LT 枠でも喋りたいと思っているんですけど, これってもう募集していないんでしょうか…

,

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

その Console コンポーネントを使った際に, 出力のユニットテストを行うための SpyOutput というものを書きました.
これは xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler)) でいうところの 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 辺りに公開するつもりです.

, ,

便利そうだったので PHP でも実装しました.
Packagist に公開したので Composer でインストールできます.

何をするものかというと, 例えば HTTP リクエストなど, 失敗する可能性のある処理をリトライしよう, というときの処理を抽象化するためのものです.

インストール

Composer を使用するので, 適当なディレクトリに composer.json を用意します.

そして以下のコマンドを実行すると Composer 自体をインストールした後, Composer で RetryHandler が ./vendor ディレクトリにインストールされます.

使い方

無名関数をラップして, リトライしたい処理を Proc オブジェクトとして生成します.
retry() メソッドを呼び出すと引数に呼び出した数だけリトライ処理を行い, 全て失敗した場合は RetryOverException がスローされます.

リトライ対象の例外を指定する

デフォルトでは RuntimeException かそのサブクラスがスローされた場合のみリトライを行い, それ以外 (例えば LogicException など) の例外がスローされたら強制的に終了となります.

あるライブラリがスローする例外のみを想定してリトライし, それ以外はエラーとする, といった場合は accepted_exception オプションを使用することで実現できます.

待ち時間を入れる

リトライの前に待ち時間を入れたい, というときは wait オプションを使い, 待ち時間を秒単位で指定できます.
デフォルトでは 1 秒になっています.

まとめ

元の Ruby の実装だと Parallel や Thread を使ったとてもクールな実装になっていて, 素の PHP だとそこまではできなさそうな感じではありますが, リトライ処理自体は PHP でも割とやると思うので, 奇麗に書けるといいんじゃないかと思います.

RetryHandler はとりあえず動いた, 程度のレベルで, まだまだ使えない (例えば, Proc の中の値どうやって取るんだ, とか) と思うんですが, その辺りは今後気まぐれで改善していければという感じです.

ところで 2 年程まえにちょうど同じような需要があって作った Retrial というものがあるんですけど, 今見るととても酷いコードで, テストも何がやりたいのかわからない感じ.
まぁこの頃よりはまだマシなコードが書けるようになったんだなと自分を納得させています.