内部クラスを持つユニットテストクラスへの正しい方法

クラスAには内部クラスBがあります。クラスAには、クラスBオブジェクトのプライベート・リストがあり、getBs、addB、およびremoveBメソッドを通じて使用できます。 removeBメソッドを単体テストするにはどうすればよいですか?

私は、クラスBの2つの等しいモックを作成し、それぞれを追加し、次にそれらのうちの1つを2回削除することを望んでいました(その結果、両方を削除することになりました)。しかし、私はそれ以来、equalsメソッドはモックオブジェクトに対して呼び出されないということを、失敗を通して学びました。

単体テストのために外部クラスを内部クラスから分離しようとするのは愚かですか(または不可能)でしたか?


サンプルコードは以下のとおりです

Class to test:

import java.util.ArrayList;
import java.util.List;

public class Example {
    private List inners = new ArrayList();

    public List getInners() {
        return inners;
    }

    public void addInner(Inner i) {
        inners.add(i);
    }

    public void removeInner(Inner i) {
        inners.remove(i);
    }

    /**
     * equalityField represents all fields that are used for testing equality
     */
    public class Inner {
        private int equalityField;
        private int otherFields;

        public int getEqualityField() {
            return equalityField;
        }

        public Inner(int field) {
            this.equalityField = field;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null)
                return false;
            if (o.getClass() != this.getClass())
                return false;
            Inner other = (Inner) o;
            if (equalityField == other.getEqualityField())
                return true;
            return false;
        }
    }
}

非常にうまくいかなかったテストケース:

import static org.junit.Assert.*;
import org.junit.Test;
import org.easymock.classextension.EasyMock;

public class ExampleTest {
    @Test
    public void testRemoveInner() {
        Example.Inner[] mockInner = new Example.Inner[2];
        mockInner[0] = EasyMock.createMock(Example.Inner.class);
        mockInner[1] = EasyMock.createMock(Example.Inner.class);        

        Example e = new Example();
        e.addInner(mockInner[0]);
        e.addInner(mockInner[1]);
        e.removeInner(mockInner[0]);
        e.removeInner(mockInner[0]);
        assertEquals(0, e.getInners().size());
    }
}
4
完全に無関係な場合は、外部クラスへの参照を必要としない場合は静的内部クラスを使用します
追加された 著者 phoet,

4 答え

まず、あなたの質問への答え:はい、一般的にユニットテストのときに内部クラスと外部クラスを分離してみることは悪い考えです。通常、これは2つが密接にリンクされているためです。たとえば、InnerはOuterのコンテキストでのみ意味があり、Outerはインターフェイスの実装であるInnerを返すファクトリメソッドを持っています。 2つが本当にリンクされていない場合は、それらを2つのファイルに分けます。テストの生活を楽にします。

第二に、(上のコードを例として使用して)、あなたは実際に上記のコードを嘲笑する必要はありません。ちょうどいくつかのインスタンスを作成し、離れて行く。あなたは十分に仕事をしているようです。あなたはいつも次のようなことをすることができます:

public void testRemoveInner() {
    Example.Inner[] inner = new Example.Inner(45);

    Example e = new Example();
    e.addInner(inner);
    e.addInner(inner);
    e.removeInner(inner);
    assertEquals(0, e.getInners().size());
}

モックは必要ありません。

第三に、あなたが実際にテストしているものを試してみてください。上記のコードでは、リストに何かを追加すると、それを削除できることをテストしています。ちなみに、 '等しい'という複数のオブジェクトがあるとします。上記のコードでは、コレクション#remove()

指定された要素の単一のインスタンスをthisから削除します。   コレクションが存在する場合はそれを返します(オプションのオペレーション)。より正式には、   (o == null?e == null:o.equals(e))のような要素eを削除しますif   このコレクションには、そのような要素が1つ以上含まれています。

それは本当にあなたがテストしたいものですか?

第4に、equalsを実装する場合は、hashCodeも実装します( JavaでのequalsおよびhashCodeのオーバーライド)。

5
追加された

なぜあなたは内部クラスを模倣しなければならないのですか?私がこの問題に直面した場合、クラスをそのまま使用し、外部クラスの動作が期待通りに動作することをテストします。あなたはそれを行うために内部クラスを模擬する必要はありません。

さて、equals()メソッドをオーバーライドする場合、hashCode()メソッドもオーバーライドすることをお勧めします。

4
追加された
私はこの答えが理にかなっているとは思わない。内部クラスと外部クラスの間にはいくらかのやりとりがあり、テストが必要なことは明らかです。なぜこれは受け入れられた答えですか?
追加された 著者 user239558,
equals/hashcodeについて:ハッシュコードをオーバーライドすることをお勧めします。Javaの重要な契約を破ったり、ハッシュテーブルでクラスを使用できなくしたり、実行時に奇妙なバグを生成したりすることはできません。
追加された 著者 Guillaume,
あまりにも遅いが、私は誰かがモックを望む理由について何らかの推論を提供すべきだと思った。内部環境は、テスト環境では起こらないべきネットワークIOを担当している。
追加された 著者 OverlordAlex,

初めてこの状況に遭遇したTDD newbとして、私はリファクタリングの結果として「テスト可能でない」内部クラスを持つことに気付きました。そして、「純粋な」TDDアプローチを使用すると、内部クラスを他の方法で使用します。

問題は、内部クラスから外部クラスオブジェクトに1つ以上の参照が行われたと仮定すると、この特定のリファクタリングはしばしば1つ以上のテストを中断します。この理由はかなり簡単です:あなたの模擬オブジェクトは、 spy なら実際には実際のオブジェクトを包むラッパーです

MyClass myClass = spy( new MyClass() );

...内部クラスは常に実際のオブジェクトを参照するので、 myClass にモックを適用しようとすると動作しないことがよくあります。さらに悪いことに、モックがなくても、その事実が完全に理解できなくなり、通常のビジネスに転じる可能性が高くなります。あなたのスパイがそれ自体のために実際のコンストラクタメソッドを実行していないことにも注意してください。

私たちのテストの開発が品質への投資であることを考えると、「OK、私はそのテストを中止するつもりです」と言うのはひどい恥です。

私は2つのオプションがあることをお勧めします:

  1. 内部クラスの直接外部クラスフィールドへのアクセスをgetter/setterメソッド(これは private それは、使用される模擬の方法です...そして、模擬の分野です。あなたの既存のテストは、引き続き合格するはずです。

  2. もう一つの可能​​性は、内部クラスを内部クラスに置き換えてインスタンスを内部クラスに置き換え、この新しいクラスの新しいテストクラスに1つ以上のテストメソッドを移すことです。次に、外部クラスオブジェクトへの参照がパラメータ化されている(つまり、ケースの99%がコンストラクタパラメータとして渡されている)ようにリギングする(単純な)タスクに直面し、適切に嘲笑することができます。これはあまり難しくありません。外部クラスの private フィールドに適切なgetter/setterメソッドを追加するか、その private メソッドの1つ以上を package-private 外部クラスのテストに関しては、内部クラスのその時点から「ブラックボックス」になります。

いずれの方法を使っても、品質の低下はありませんでした。

1
追加された

あなたはテスト目的のクラス(A)を目的のATestクラスで拡張することができます。このクラスは、Bのプライベートリスト

0
追加された