レガシーコード改善ガイド 9章このクラスをテストハーネスに入れることができません

9章このクラスをテストハーネスに入れることができません

レガシーコード改善ガイド

テストしたいクラスがあります。しかし、テストケース内でインスタンス生成が難しい。その状況ごとにどのように対応すればいいかを説明しています。

大きく分けると5パターン

//(1)コンストラクタ注入のオブジェクトの生成が難しい public void testFuga() { SomeConnection conn = new SomeConnection(); Fuga fuga = new Fuga(conn); } // (2)クラス内で、newしているオブジェクトの生成が難しい public void testFuga() { Fuga fuga = new Fuga(); } class Fuga { public Fuga() { this.conn = new SomeConnection(); } } // (3)Singletonへアクセスしている class Fuga { public Fuga() { this.hoge = MySingleton.getInstance(); } } // (4)条件によって、親クラス、子クラスを使い分けているプロパティがある // BaseHoge、MyHogeとクラス階層があるため、IMyHogeインターフェースを作っても、BaseHoge型に代入できない。 class Fuga { public Fuga() { if (someFlag) { this.hoge = new BaseHoge(); } else { this.hoge = new MyHoge(); } } } class MyHoge extends BaseHoge { } // (5)C++ の #include依存関係
Code language: Java (java)
コンストラクタ注入のオブジェクトクラス内で、newしているオブジェクトSingleton親クラス、子クラスを使い分けている
9.1 いらだたしいパラメータ9.6 玉ねぎパラメータ9.2 隠れた依存関係9.3 複雑な生成9.4 いらだたしいグローバルな依存関係9.7 別名のパラメータ
この章で説明あり
インターフェースの抽出(377)
Nullを渡す(123)Nullオブジェクト(124)
サブクラス化とメソッドのオーバーライド(415)
コンストラクタのパラメータ化(394)+シグネチャの維持(328)
インスタンス変数の入れ替え(418)
静的setメソッドの導入(386)
この章で説明なし
getメソッドの抽出とオーバーライド(368)
FactoryMethodの抽出とオーバーライド(366)
実装の抽出(371)
メソッドのパラメータ化(398)
呼び出しの抽出とオーバーライド(363)

9.7 別名のパラメータ

それは、コンストラクタに対して渡すべきOriginationPermitを作ることが困難なことです。OriginationPermitは恐るべき依存関係を持っています。

p147

OriginationPermitのインターフェース抽出して、フェイクをコンストラクタ注入したい、だけど、それは難しいって言っているけど、よくわからないよ

次のような状況よね

// テスト対象 public class lndustrialFacilityextendsFacility { Permit basePermit; public lndustrialFacilityextendsFacility(Permit originationPermit) { if (flag) { permit = originationPermit } else { permit = new Permit(); } } } // Permitのクラス階層 class Permit { } class FacilityPermit extends Permit { } class OriginationPermit extends FacilityPermit { }
Code language: Java (java)

IOriginationPermitインタフェース型をPermit型のフィールドに代入しなければなりませんが、それは機能しないでしょう。なぜならJavaでは、インタフェースがクラスを継承できないためです。

p148

むむ?

やってみましょ

// インターフェースを定義して interface IOriginationPermit { } // Fakeクラスを定義して // (FacilityPermitクラスやPermitクラスを継承していないことに注目) class FakeOriginationPermit implements IOriginationPermit { } // テストケース // Fakeクラスのインスタンスを渡そうとすると、コンパイルエラー void test() { new lndustrialFacilityextendsFacility( new FakeOriginationPermit() // ★ココで、java: 不適合な型: <anonymous IOriginationPermit>をPermitに変換できません: ); }
Code language: Java (java)

コンパイルエラーになっちゃったよ...

FakeOriginationPermitクラスは、Permitクラスを継承していないからね

最もわかりやすい解決方法は、すべてをたどってそれぞれにインタフェースを作成し、PermitのフィールドをIPermitのフィールドに変えることです。図9.5はこの状況を表します。これはとんでもない量の仕事になりますし、私はコードをこんな風にしてしまうことを好みません。
...
しかし他の選択肢があるのなら、それを検討すべきです。

p148

どういうこと?

クラス階層に対応した、インターフェース階層を用意するってことね

// インターフェースの継承階層を定義して interface IPermit { } interface IFacilityPermit extends IPermit { } interface IOriginationPermit extends IFacilityPermit { } // 元のクラスにインターフェースを定義して class Permit implements IPermit { } class FacilityPermit extends Permit implements IFacilityPermit { } class OriginationPermit extends FacilityPermit implements IFacilityPermit { } // テスト対象の Permit型 を IPermit に変更する public class lndustrialFacilityextendsFacility { IPermit basePermit; }
Code language: Java (java)

面倒そうだね...

この場合は「サブクラス化とメソッドのオーバーライド」を使えたそうよ

public class OriginationPermit extends FacilityPermit { public void validate() { // データベースに接続する // 許可情報を問い合わせる // 許可フラグをセットする // データベースを閉じる } }
Code language: Java (java)

テストケース内で、このvalidateメソッドが呼ばれるらしいんだけど、データベースには接続したくないのね

まず、abstractなFakeクラスを定義しておいて
(サブクラス化)

public abstract class FakeOriginationPermit extends OriginationPermit { abstract public void validate(); }
Code language: Java (java)

その場で必要なvalidateメソッドを持つインスタンスを作るの
(メソッドのオーバーライド)

public void testHasPermits() { class AlwaysValidPermit extends FakeOriginationPermit { public void validate() { // 許可フラグをセットする(データベースには接続しない) } }; Facility facility = new lndustrialFacility(new AlwaysValidPermit()); }
Code language: Java (java)

苦労して OriginationPermitクラスのFakeを用意したけど、本当にテストしたいのは、IndustrialFacilityクラスなんだよね。頭がこんがらがっちゃうよ...

タイトルとURLをコピーしました