レガシーコード改善ガイド 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をコピーしました