OOの原則とテストのための原則破り

を読んでいます。で、まぁいろいろ Twitter の方には流しているわけですが、とりあえず

テスト用のクラスで本番コードと一部挙動を変えてしまう

という、これまでの自分にはなかった発想が書かれていたので試しに PHP で書いてみました。

Singleton を Singleton じゃなくしてテストする

テスティングフレームワークは今は亡き pearified.com の SimpleTest を使っているのでその辺は適宜書き換え、読み替えてください。

ここでは

Singleton なクラスをテストのときだけ様々なパターンでインスタンス生成したい

ケースを想定しています。実際、よくあると思います。そのために

  1. コンストラクタの可視性を変更し
  2. instance() メソッドの戻り値を変更

しています。

どちらかだけで「本当は Singleton なんだけどテストのときだけ普通のクラス」という状態を実現できます。

1 の場合はたまたまコンストラクタですが、同じ要領でテストのときだけメソッドを public にしてテストすることができます。まぁ今回のケースのように「中身がなくて可視性を変えるだけ」であれば簡単ですが、普段はあまりやらないかも。それよりも

どうしても直接テストしたいメソッドは public にする

という方法の方がラクチンだし確実ですね。

2 は本当はメソッドの挙動が変わってしまう(何回呼んでも同じ値が返ってくるわけではない)ので、リスコフの置換原則には反しているような気がします。でもこれも有効なテストのための手法の一つだなぁと思います。Singleton じゃない場合でも

依存オブジェクトの生成処理がメソッドの中にあればオーバーライドして Fake オブジェクトを作るのが容易になります。

そうすると簡単に依存性を排除できます。

cf.

PHP: spl_object_hash - Manual

PHP 5.2.0 以降ではオブジェクトの同一性をハッシュ値で確認できます。

何が大事なことなのか

今回やったことは、恐らく OO の大きなメリットの一つであるカプセル化に反しています。また恐らくリスコフの置換原則にも反しているんじゃないかと思います。しかし、おかげでテストが書きやすくなり、コードの挙動をコードで記述し、捕捉することが可能になり、コードが理解しやすくなり、結果、ソフトウェアのメンテナンス性は上がります。

カプセル化とテストによる保護は必ずしも対立しませんが、それらが対立する場合、私はテストによる保護を優先します。そのほうが、将来カプセル化を強めるために役立つことが多いからです。

カプセル化はそれ自体が目的ではありません。それは理解するための手段です。

(『レガシーコード改善ガイド』 初版第2刷 p186)

本当に大事なことは盲目的に OO の教義に従うことではなくて、より良い品質のソフトウェアを書き、その価値を届けることなわけで、OO の教義も今回の原則違反もそのための手段なんですよね。

もしかすると TDD の本やリファクタリングなんかにはこの手のことがすでに書かれているのかもしれませんが、目から鱗でした。どんどん活かしていこうと思います。

cf.

More