12

JUnit テストを作成して、以下のコードが BufferedInputStream を使用していることを確認したいと思います。

public static final FilterFactory BZIP2_FACTORY = new FilterFactory() {
    public InputStream makeFilter(InputStream in) {        
        // a lot of other code removed for clarity 
        BufferedInputStream buffer = new BufferedInputStream(in);
        return new CBZip2InputStream(buffer);
    }
};

(FilterFactory はインターフェースです。)

これまでの私のテストは次のようになります。

@Test
public void testBZIP2_FactoryUsesBufferedInputStream() throws Throwable {
    InputStream in = mock(InputStream.class);
    BufferedInputStream buffer = mock(BufferedInputStream.class);
    CBZip2InputStream expected = mock(CBZip2InputStream.class);

    PowerMockito.spy(InputHelper.BZIP2_FACTORY);  // This line fails
    whenNew(BufferedInputStream.class).withArguments(in).thenReturn(buffer);
    whenNew(CBZip2InputStream.class).withArguments(buffer).thenReturn(expected);
    InputStream observed = InputHelper.BZIP2_FACTORY.makeFilter(in);

    assertEquals(expected, observed);
}

PowerMockito.spy を呼び出すと、次のメッセージで例外が発生します。

org.mockito.exceptions.base.MockitoException: 
Mockito cannot mock this class: class edu.gvsu.cis.kurmasz.io.InputHelper$1
Mockito can only mock visible & non-final classes.

whenNew の呼び出しを設定するには、PowerMocktio.spy の代わりに何を使用すればよいですか?

4

4 に答える 4

14

メッセージは非常に明白です。非表示の最終クラスをモックすることはできません。簡単な答え:匿名のクラスの名前付きクラスを作成し、代わりにこのクラスをテストしてください!

長い答え、理由を掘り下げましょう!

匿名のクラスは最終です

の匿名クラスをインスタンス化しますFilterFactory。コンパイラが匿名クラスを検出すると、最終的なパッケージ表示クラスを作成します。したがって、匿名クラスは、標準的な手段、つまりMockitoを介してモックすることはできません。

匿名クラスのモック:可能ですが、HACKYでない場合はBRITTLE

では、Powermockを介してこの匿名クラスをモックできるようにしたいとします。現在のコンパイラは、次のスキームで匿名クラスをコンパイルします。

Declaring class + $ + <order of declaration starting with 1>

匿名クラスをあざけることは可能ですが、もろいです(つまり、それを意味します)したがって、匿名クラスが宣言される11番目であるとすると、次のように表示されます。

InputHelper$11.class

したがって、匿名クラスのテストの準備をする可能性があります。

@RunWith(PowerMockRunner.class)
@PrepareForTest({InputHelper$11.class})
public class InputHelperTest {
    @Test
    public void anonymous_class_mocking works() throws Throwable {
        PowerMockito.spy(InputHelper.BZIP2_FACTORY);  // This line fails
    }
}

このコードはコンパイルされますが、最終的にはIDEのエラーとして報告されます。IDEはおそらくについて知りませんInputHelper$11.class。コードレポートをチェックするためにコンパイルされたクラスを使用しないIntelliJ。

また、匿名クラスの命名が実際には宣言の順序に依存するという事実も問題です。誰かが以前に別の匿名クラスを追加すると、番号が変更される可能性があります。匿名クラスは匿名のままになります。コンパイラーがいつか文字やランダムな識別子を使用することにした場合はどうでしょうか。

したがって、Powermockを介して匿名クラスをモックすることは可能ですが、脆弱です。実際のプロジェクトでは絶対に行わないでください。

編集済み注: Eclipseコンパイラには異なる番号付けスキームがあり、常に3桁の番号を使用します:

Declaring class + $ + <pad with 0> + <order of declaration starting with 1>

また、JLSは、コンパイラが匿名クラスに名前を付ける方法を明確に指定していないと思います。

スパイを静的フィールドに再割り当てしません

PowerMockito.spy(InputHelper.BZIP2_FACTORY);  // This line fails
whenNew(BufferedInputStream.class).withArguments(in).thenReturn(buffer);
whenNew(CBZip2InputStream.class).withArguments(buffer).thenReturn(expected);
InputStream observed = InputHelper.BZIP2_FACTORY.makeFilter(in);

PowerMockito.spyスパイを返しますが、の値は変更されませんInputHelper.BZIP2_FACTORY。したがって、このフィールドをリフレクションを介して実際に設定する必要があります。WhiteboxPowermockが提供するユーティリティを使用できます。

結論

匿名フィルターがを使用するモックでテストするにはあまりにも多くの問題がありBufferedInputStreamます。

私はむしろ次のコードを書きたいです:

名前付きクラスを使用する入力ヘルパーです。このフィルターの目的をユーザーに明確にするために、インターフェイス名は使用しません。

public class InputHelper {
    public static final BufferedBZIP2FilterFactory BZIP2_FACTORY = new BufferedBZIP2FilterFactory();
}

そして今、フィルター自体:

public class BufferedBZIP2FilterFactory {
    public InputStream makeFilter(InputStream in) {
        BufferedInputStream buffer = new BufferedInputStream(in);
        return new CBZip2InputStream(buffer);
    }
}

今、あなたはこのようなテストを書くことができます:

@RunWith(PowerMockRunner.class)
public class BufferedBZIP2FilterFactoryTest {

    @Test
    @PrepareForTest({BufferedBZIP2FilterFactory.class})
    public void wraps_InputStream_in_BufferedInputStream() throws Exception {
        whenNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class))
                .thenReturn(Mockito.mock(CBZip2InputStream.class));

        new BufferedBZIP2FilterFactory().makeFilter(anInputStream());

        verifyNew(CBZip2InputStream.class).withArguments(isA(BufferedInputStream.class));
    }

    private ByteArrayInputStream anInputStream() {
        return new ByteArrayInputStream(new byte[10]);
    }
}

CBZip2InputStreamただし、を強制的に受け入れる場合は、最終的にこのテストシナリオのpowermockを回避できますBufferedInputStream。通常、Powermockを使用するということは、設計に問題があることを意味します。私の意見では、Powermockはレガシーソフトウェアには最適ですが、新しいコードを設計するときに開発者を盲目にする可能性があります。彼らはOOPの良い部分のポイントを欠いているので、私は彼らがレガシーコードを設計しているとさえ言うでしょう。

お役に立てば幸いです。

于 2012-02-27T15:24:52.670 に答える
9

古い投稿ですが、名前付きクラスを作成する必要はありません - この投稿に記載されているように、代わりにワイルドカードを使用します

@PrepareForTest(fullyQualifiedNames = "com.yourpackage.containing.anonclass.*")

于 2014-05-09T00:58:45.510 に答える
0

私はちょうど同じ問題に遭遇しました。したがって、コンストラクターのモックのドキュメントによると、クラスを準備する必要があります。これにより、邪悪なクラスが作成されます。あなたの場合、邪悪なクラスはBufferedInputStreamとCBZip2InputStreamであり、それらの作成者は匿名クラスであり、PrepareForTestアノテーションで定義することはできません。だから私はあなたがしたのと同じことをしなければなりませんでした(うーん、あなたのコメントを見ただけです)、私は匿名クラスを名前付きクラスに移動しました。

于 2012-02-24T21:27:04.713 に答える
0

PowerMockito ランナーを使用してテストを実行する必要があり、どのクラスにカスタム動作を持たせる必要があるかをフレームワークに伝える必要があります。テスト クラスに次のクラス アノテーションを追加します。

@RunWith(PowerMockRunner.class)
@PrepareForTest({ BufferedInputStream.class })
于 2011-10-31T15:47:55.330 に答える