2008-09-14 9 views
35

私のモットーは「Javaには静的なブロックがあるからといって、それを使うべきではない」ということです。ジョークを除いて、Javaには悪夢をテストするためのたくさんのテクニックがあります。私が気に入っているのは、匿名クラスと静的ブロックです。スタティックブロックを使用する多くのレガシーコードがあります。これらは単体テストを書く上での迷惑な点の1つです。私たちの目標は、最小限のコード変更でこの静的初期化に依存するクラスの単体テストを書くことができるようにすることです。Javaの静的ブロックを擬似する

これまでのところ私の同僚の提案は、静的ブロックの本体をプライベート静的メソッドに移動し、staticInitと呼ぶことです。このメソッドは、静的ブロック内から呼び出すことができます。ユニットテストのために、このクラスに依存する別のクラスは、何もしないためにstaticInitをJMockitで簡単に嘲ることができます。例を見てみましょう。我々はJUnitの中で次の操作を実行できるようにするため

​​

public class ClassWithStaticInit { 
    static { 
    staticInit(); 
    } 

    private static void staticInit() { 
    System.out.println("static initialized."); 
    } 
} 

に変更されます。

public class DependentClassTest { 
    public static class MockClassWithStaticInit { 
    public static void staticInit() { 
    } 
    } 

    @BeforeClass 
    public static void setUpBeforeClass() { 
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class); 
    } 
} 

ただし、この解決策には独自の問題もあります。静的ブロックを実際にClassWithStaticInitTestに実行させたいので、DependentClassTestClassWithStaticInitTestを同じJVM上で実行することはできません。

このタスクを達成するあなたの方法は何ですか?または、JMockit以外の優れたソリューションで、よりクリーンな作業ができると思っていますか?

答えて

5

私はこの問題に遭遇したとき、私は通常、私はそれを手動で起動することができるように保護された静的メソッドを作る以外、あなたが記述と同じことを行います。さらに、メソッドが問題なく何回も呼び出せることを確認します(それ以外の場合は、テストが行​​われる限り静的イニシャライザより優れていません)。

これは合理的にうまく動作し、実際に静的初期化メソッドが期待しているかどうかをテストできます。場合によっては、静的な初期化コードを持つのが最も簡単な場合もあり、それを置き換えるために複雑すぎるシステムを構築する価値はありません。

このメカニズムを使用するときは、保護されたメソッドがテスト目的でのみ公開されていることを文書化してください。他の開発者がそのメソッドを使用することは望ましくありません。もちろん、これは実行可能な解決策ではないかもしれません。たとえば、クラスのインタフェースが外部から見える場合(他のチームのための何らかのサブコンポーネントとして、または公開フレームワークとして)です。それは問題への簡単な解決ですが、サードパーティのライブラリをセットアップする必要はありません(私はそれが好きです)。

4

あなたは症状を治療しているように私に聞こえます。静的初期化に依存する設計が貧弱です。たぶん、いくつかのリファクタリングが本当の解決策かもしれません。 staticInit()関数で少しリファクタリングしたようですが、その関数を静的イニシャライザではなくコンストラクタから呼び出す必要があります。静的イニシャライザの期間をなくすことができれば、あなたはもっと良いでしょう。あなただけがこの決定を下すことができます(あなたのコードベースは見えませんが)いくつかのリファクタリングが間違いなく助けになります。

mockingに関しては、私はEasyMockを使用していますが、私は同じ問題を抱えています。従来のコードでの静的初期化子の副作用は、テストを困難にします。私たちの答えは静的イニシャライザをリファクタリングすることでした。

+0

EasyMockで 'static'メソッドや' private'メソッドをモックすることはできません。 静的初期化子の本体を移動することは、今のところできるリファクタリングのところまでです。 –

+0

テスト対象のクラスに静的なinit/privateメソッドがある場合は、それを呼び出す必要があります。問題ない。しかし、クラスが嘲笑されている場合は、簡単なモックでは問題ありません。実装がないため呼び出されません。プライベートなものが存在しないかのように、パブリックインターフェイスを安全にモックすることができます。 –

1

静的なイニシャライザではなく、実際に何らかのファクトリが必要だと思います。

シングルトンと抽象ファクトリを混在させると、おそらく今日と同じ機能性とテスト容易性を得ることができますが、それはかなりのボイラープレートコードを追加することになります。静的なものを完全に取り除こうとするか、少なくとも複雑ではない解決策を取り除くことができるかどうかを試してみてください。

あなたのコードを見ることなくそれが可能かどうかは分かりません。

3

あなたはGroovyでテストコードを書いて、簡単にメタプログラミングを使って静的メソッドをモックすることができます。

Math.metaClass.'static'.max = { int a, int b -> 
    a + b 
} 

Math.max 1, 2 

Groovyを使用できない場合は、コードをリファクタリングする必要があります(初期化子のようなものを注入する必要があります)。

種類よろしく

+0

私はGroovyが大好きですが、残念ながらすべてのテストコードはJUnitになければなりません。 –

+0

Cem、 しかし、junit(junit4まで)テストはgroovyで書くことができます。 ;-) 親切にもらえます – marcospereira

1

私はMockフレームワークに精通していませんので、私が間違っていれば私を修正してください。あなたが言及した状況をカバーするために2つの異なるMockオブジェクトを持つことはできませんでしたか?このような

public static class MockClassWithEmptyStaticInit { 
    public static void staticInit() { 
    } 
} 

public static class MockClassWithStaticInit { 
    public static void staticInit() { 
    System.out.println("static initialized."); 
    } 
} 

として次に、あなたのさまざまなテストケースをそれぞれ

@BeforeClass 
public static void setUpBeforeClass() { 
    Mockit.redefineMethods(ClassWithStaticInit.class, 
         MockClassWithEmptyStaticInit.class); 
} 

@BeforeClass 
public static void setUpBeforeClass() { 
    Mockit.redefineMethods(ClassWithStaticInit.class, 
         MockClassWithStaticInit.class); 
} 

でそれらを使用することができます。

+0

'System.out.println(" static initialized。 ");'は、静的イニシャライザが最初にやっていることです。なぜ同じことをやっているのか、私たちは嘲笑すべきですか? –

+0

セム、私はあなたが私のことを忘れたと思う。私は心配に答えようとしていますが、このソリューションには独自の問題もあります。ClassWithStaticInitTestで静的ブロックを実際に実行したいので、DependentClassTestとClassWithStaticInitTestを同じJVMで実行することはできません。 – martinatime

+0

基本的には、2つの異なるMock Object to Mock ClassWithStaticInitを使用して、DependentClassTestで使用するためのものと、ClassWithStaticInitTestで使用するためのものを用意します。 – martinatime

10

これは、「高度な」JMockitになる予定です。それは、public void $clinit()メソッドを作成することで、JMockitで静的初期化ブロックを再定義することができます。だから、あるとして代わりに

public class ClassWithStaticInit { 
    static { 
    staticInit(); 
    } 

    private static void staticInit() { 
    System.out.println("static initialized."); 
    } 
} 

この変更を行うの我々としてもClassWithStaticInitを残すかもしれないし、MockClassWithStaticInitで次の操作を行います。

public static class MockClassWithStaticInit { 
    public void $clinit() { 
    } 
} 

これは実際に私たちは中に変更を加えないでできるようになります既存のクラス。

+0

これは受け入れられた答えです。また、 @Mockアノテーション付きの$ clinitではパブリックアクセサーは必要ありません。 – user2219808

32

PowerMockは、EasyMockとMockitoを拡張する別の模擬フレームワークです。 PowerMockを使用すると、静的イニシャライザなどのクラスから簡単にremove unwanted behaviorを呼び出すことができます。あなたの例では単にあなたのJUnitテストケースに以下の注釈を追加:

@RunWith(PowerMockRunner.class) 
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit") 

PowerMockは、Javaエージェントを使用していないため、JVMの起動パラメータの変更を必要としません。単純にjarファイルと上記のアノテーションを追加します。

9

時々、私のコードが依存するクラスで静的な初期化子を見つけることがあります。私はコードをリファクタリングすることができない場合は、私は、静的初期化子を抑制するためにPowerMock@SuppressStaticInitializationFor注釈を使用します。

@RunWith(PowerMockRunner.class) 
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit") 
public class ClassWithStaticInitTest { 

    ClassWithStaticInit tested; 

    @Before 
    public void setUp() { 
     tested = new ClassWithStaticInit(); 
    } 

    @Test 
    public void testSuppressStaticInitializer() { 
     asserNotNull(tested); 
    } 

    // more tests... 
} 

suppressing unwanted behaviourについては、こちらをご覧ください。

免責事項:PowerMockは、私の2人の同僚が開発したオープンソースプロジェクトです。

0

答えはありませんが、ちょうど不思議です。Mockit.redefineMethodsへの呼び出しを「逆転」する方法はありませんか?
このような明示的なメソッドが存在しない場合は、次のようにして再度実行するべきではありませんか?

Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class); 

ような方法が存在する場合は何も同じJVMから、変わっていないかのように、あなたは、 『オリジナル』静的初期化子ブロックでクラスの@AfterClass方法、およびテストClassWithStaticInitTestでそれを実行する可能性があります。

これはちょうど感傷的なので、私は何かが不足している可能性があります。

関連する問題