検出と分離
変更する前の理想の状態
特に何も手を入れなくてもテストが書けるようなクラスが理想。
依存関係が多いと、対象のクラスをテストするのにいろんなクラスのインスタンスを生成しなければならなくなる。
上記の理由からテストを実行するには依存関係を排除するのがよい。
しかし理由はそれだけではなく、テスト対象のクラスが依存関係を持っているクラスに対しての影響がどうあるのかを把握しておく必要がある。
検出と分離
テストを整備する際に依存関係を排除する理由は2つ。
- 検出 ー コードの計算した値にアクセスできないときに、それを検出するために依存関係を排除する
- 分離 ー コードをテストハーネスに入れて実行すらできないとき、分離するために依存関係を排除する
例:NetworkBridgeクラス
- EndPointクラスはそれぞれローカルのsocketを開き、EndPointと通信をする
- 以前あった、単体テストのようで単体テストではない例として、ネットワークの通信をしているというのがあった。これは単体テストを作成することができない
- テスト対象のクラスでないものに関しては、できるだけ考えなくて済むようにするのがよい
public class NetworkBridge
{
public NetworkBridge(EndPoint[] endpoints) {
...
}
public void formRouting(String sourceID, String destID) {
...
}
}
検出と分離の方法
分離の方法は巻末に記載。すでに出てきたものとしては「インターフェースの抽出」「パラメータのプリミティブ化」。
しかし、検出のための主な手段は一つしかない → 「協調クラスの擬装」
協調クラスの擬装
変更対象のクラスが協調しているクラスは変更対象のクラスのテストには無関係なので、依存関係を排除したい。
変更対象のクラスが協調しているクラスを別のクラスに置き換えてインスタンス化したい。
そのオブジェクトを擬装オブジェクトという。
擬装オブジェクト
擬装オブジェクトとはクラスのテストを行うときにその協調クラスになりすますオブジェクトのこと。
例:Saleクラスのscanメソッドをテストしたい
- Saleクラスはscanメソッドを持ち、scanメソッドは引数に文字列をとって、文字列(バーコード)の品物の名前と価格をレジの画面に出力する
- scanメソッドのテストの方法は、通常Saleクラスをインスタンス化して、scanメソッドにバーコードの文字列を渡す
- レジに表示されている品物の名前と価格を確認する
- しかし、レジの画面への出力方法がSaleに埋め込まれているとテストできない依存関係が発生していることになる
依存関係の排除
- レジに出力する処理用のクラスを作成し、処理をその新しいクラスに委譲する
- インターフェースの抽出を行い、テスト用のディスプレイクラスを作成する
ディスプレイに出力される文字を取得するメソッド自体は必要ないが、テストとして正しい文字列が出力されているかを確認するために、テスト用のメソッド(getLastLine())をテスト用クラスに追加している。
これによって、擬装オブジェクトの2つの側面(テストからのみ参照されるインターフェースと実際に利用するクラスから参照されるインターフェース)が生まれる。
モックオブジェクト
テストをするために擬装オブジェクトをたくさん生成しないといけない場合はモックオブジェクトを検討してくださいとのこと。
モックオブジェクトとは内部的に表明(assertion)を実行する擬装オブジェクトのこと。
public class SaleTest extends TestCase
{
public void testDisplayAnItem() {
MockDisplay display = new MockDisplay();
display.setExpectation("showLine", "Milk $3.99");
Sale sale = new Sale(display);
sale.scan("1");
display.verify();
}
}
こうなるべきというものをexpectationでセット、verify()で確認させるという流れ。