「実装をテストする」とは?

TDD界隈の議論で、「仕様のテスト」「実装のテスト」という話を聞くことがあります。

今日のTDD界隈で「仕様のテスト」「実装のテスト」という言い回しを一番よくしているのは私だと思うのですが、勉強会の場などでは話をすることはあるものの、こういう形で残してこなかったので、自分の考えをまとめたいと思います。

公開されているインターフェースの仕様を満たせるなら、API(「リファクタリング」で言う「公布済みインターフェース」)のエントリポイントの内側のクラス設計をどのように組み立てるかは、実装者の裁量に任されているはずです。

品質保証の観点からは、APIの仕様を満たせるテストケースを記述すれば、ソースコードに対してのある程度のカバレッジは満たせるはずですから、内部の実装クラスに対しては、実装クラスとテストクラスが1対1になる形でのユニットテストは、原則不要となります。

このような場合、APIユースケースに対して記述するテストを「仕様のテスト」、実装クラスに対して記述するテストを「実装のテスト」と、私は読んでいます。

このような区別に基づき、「仕様のテスト」中心でユニットテストを記述する利点は、リファクタリングで内部実装の構造を変更する際に、実装クラスとテストクラス両方を変更する必要がないことです。

また、end to endでの条件に近い形でテストを行うことができるので、実装のテストを書いたときに起こりがちな「ユニットテストはパスしたけど、実際に画面と組み合わせると動かない」というリスクを低減することができます。

では、実装のテストは必要ないのか...となると、実装のテストを書く方が効果的な場合もあったりします。大きく三つのポイントがあります。

まず一点目は、仕様のテストでテストケースを表現しようとすると、テストの実装にコストがかかる場合です。例外処理のテストにありがちなのですが、仕様のテストとして表現すると、モックオブジェクトやFixture(テストデータ)のセットアップなどにコストがかかる場合は、実装に対してテストを書くことで、テストの投資対効果のバランスを保つことができます。

二点目は、内部実装の中で、オブジェクト間のメッセージのやりとりが複雑などの理由で、仕様のテストのみでは実装が難しい場合は、ピンポイントで実装のテストを書いて、実装の足場とします。

三点目は、実装者がプログラミング言語フレームワークなど、開発基盤に対する知識が不足している場合です。仕様のテストをもとにして実装を記述できるということは、実装者が、プログラム言語やフレームワークなどをどのように用いれば、仕様を実現できるか理解しているということを前提にしています。しかし実装者がそのような知識を体得していない場合は、実装の細かいレベルからユニットテストを記述しながら、テストを足場としてボトムアップで実装をくみあげていくのがいいでしょう。

この場合、前述のように「画面と組み合わせると動かない」というリスクがありますが、それについては、Seleniumのようなツールを使ってe2eでテストして確認するか、もしくは手動でテストして確認することになるでしょう。

ポイントは、局面局面に応じて、どのようなテストを書いて、バグの芽を潰していくかと言うことにあります。どんなバグが発生する可能性があって、それぞれに対してどのように網をはってリスクを潰していくかということを、場面場面で選択していくことになります。