bless を使った Casual な Test Double について
はじめに
こんにちは、tsucchi と申します。(はてなでは id:tsucchi1022 で活動してます。)
今年の YAPC::Asia で、テストについてあれこれ話させていただいたので、正直ネタがかなり尽きているのですが、最近やってみた、ちょっとヘンなテストの仕方について書いてみます。
背景
少し前に、DBD::mysqlPPというモジュールのメンテナンスを引き継いだのですが、コレ全然テストコード書いてなかったんですね。まあネタ的なモジュールだし、古いので、仕方ないのかもしれませんが。で、プレースホルダまわりの処理にバグとかセキュリティーホールとかあったりしたので、その辺を直したり、テストコードの整備を少しだけしました。
困ったこと
皆さん良く知っていると思いますが、DBD って、コンストラクタ無いんですよね。無い、というか connect がコンストラクタなので、インスタンスを作ろうとすると、DB に接続する必要があるわけです。
僕が今回テストしたい対象は、DB につなぐ必要は無いので、DB につなぎたくありません。(遅くなるし、意図しない失敗の原因になりそうなので)。データベースハンドラ($dbh)とかステートメントハンドラ($sth)のメソッドが呼べれば何でも良いわけです。
一般的なやりかた
こういうときは、いわゆる Test Double を使います。Perl だと Test::MockObject とか Test::Mock::Guard とか、(今回の例では使えませんが、DB に特化した場合だと、)DBD::Mock なんかがあります。connect を書き換えて、DB につながずにインスタンスを返す処理を書くのが、今回の例だと一般的な解法です。
でも。。。
これは CPAN モジュールで、しかも依存が非常に少ないので、無闇に依存するモジュールを増やしたくありません。
対策1
モンキーパッチして、new を作ります。(connect を書き換えるのもアリ)
BEGIN { use DBD::mysqlPP; { no warnings 'redefine'; *DBD::mysqlPP::db::DESTROY = sub {};#終了時に接続を開放する処理があるので、それを黙らせる *DBD::mysqlPP::db::new = sub { bless {}, shift }; # $dbh のnew を作った } } # ... my $dbh = DBD::mysqlPP::db->new(); # ... # $dbh を使うコード
まあこれでも良かったのですが、ステートメントハンドラ($sth)はコンストラクタに引数が必要なので、ちょっと面倒です。
対策2
むりやり bless して、インスタンスを作成してしまいます。
my $dbh = bless {}, 'DBD::mysqlPP::db'; # むりやり $dbh を作る my $sth = bless { Database => $dbh, Statement => $sql }, 'DBD::mysqlPP::st'; # 同じようにむりやり $sth を作る
普段意識することはあまり無いのですが、bless の第2引数はクラス名の文字列なので、こうすると、いつでもどこでもどんなクラスのインスタンスでも作れてしまいます。
もちろん、正規のやり方に対して不足している情報があれば、その機能は使えません。この場合だと、DB に接続して行う処理は何もできません。*1
実際のコードでは、この「対策2」の方を使ってみました。(モンキーパッチも DESTROY の書き換えに使ってますが)
まとめ、の前に
アプリケーションであれば、基本的にモジュールの依存が多少増えても気にする必要は無いと思います*2。必要な時に、必要なものを使うべきです。お気に入りの Test Double を使えば良いと思います。僕は Test::Mock::Guard を良く使っています。
あと、こういうマジックに近いヘンな方法は、それがどうしても必要な時以外は避けるべきだと思います。このモジュールについても、Test Double が必要なテストが増えてきたら、遠慮なくそれを使おうと思っています。
まとめ
bless を使って、無理やりインスタンスを生成する手法を紹介しました。
普段使う場面はあまりないと思いますが、依存を増やしたくなくて、インスタンスを作るのが面倒な場合のテスト手法の一つとして、頭の片隅の片隅くらいに入れておいていただければ、と思います。
明日は。。。誰が書くんでしょうね?