47

Java に対する私のモットーは、「Java には静的ブロックがあるからといって、それを使用する必要があるという意味ではありません」です。冗談はさておき、Java にはテストを悪夢にするトリックがたくさんあります。私が最も嫌いなのは、匿名クラスと静的ブロックの 2 つです。スタティック ブロックを利用するレガシー コードが多数ありますが、これらは単体テストの作成を推進する際の厄介なポイントの 1 つです。私たちの目標は、最小限のコード変更で、この静的初期化に依存するクラスの単体テストを記述できるようにすることです。

これまでのところ、私の同僚への提案は、静的ブロックの本体をプライベートな静的メソッドに移動し、それを呼び出すことstaticInitです。このメソッドは、静的ブロック内から呼び出すことができます。単体テストでは、このクラスに依存する別のクラスをJMockitstaticInitで簡単にモックして、何もしないようにすることができます。これを例で見てみましょう。

public class ClassWithStaticInit {
  static {
    System.out.println("static initializer.");
  }
}

に変更されます

public class ClassWithStaticInit {
  static {
    staticInit();
  }

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

JUnitで次のことができるようにします。

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

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

ただし、このソリューションには独自の問題もあります。実際には静的ブロックを に対して実行したいので、同じ JVM でDependentClassTestとを実行することはできません。ClassWithStaticInitTestClassWithStaticInitTest

このタスクを達成するためのあなたの方法は何ですか? または、よりクリーンに動作すると思われる JMockit 以外のより優れたソリューションはありますか?

4

10 に答える 10

57

PowerMockは、EasyMock と Mockito を拡張するもう 1 つのモック フレームワークです。PowerMock を使用すると、静的イニシャライザなどの不要な動作をクラスから簡単に削除できます。この例では、次の注釈を JUnit テスト ケースに追加するだけです。

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

PowerMock は Java エージェントを使用しないため、JVM 起動パラメーターを変更する必要はありません。jar ファイルと上記の注釈を追加するだけです。

于 2009-01-28T19:40:02.983 に答える
14

ときどき、コードが依存するクラスで静的初期化子を見つけることがあります。コードをリファクタリングできない場合は、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...
}

望ましくない動作の抑制について詳しくは、こちらをご覧ください。

免責事項: PowerMock は、私の 2 人の同僚によって開発されたオープン ソース プロジェクトです。

于 2011-08-30T11:20:53.420 に答える
13

これは、より「高度な」JMockit に入ります。メソッドを作成することで、JMockit で静的初期化ブロックを再定義できることがわかりましたpublic void $clinit()。したがって、この変更を行う代わりに

public class ClassWithStaticInit {
  static {
    staticInit();
  }

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

そのままClassWithStaticInitにして、 で次のことを行うこともできますMockClassWithStaticInit

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

実際、これにより、既存のクラスに変更を加えることができなくなります。

于 2008-09-28T00:38:20.323 に答える
6

あなたが症状を扱っているように思えます:静的初期化に依存する貧弱な設計。おそらく、いくつかのリファクタリングが本当の解決策です。関数を少しリファクタリングしたように聞こえますstaticInit()が、おそらくその関数は、静的初期化子からではなく、コンストラクターから呼び出す必要があります。静的初期化子の期間をなくすことができれば、より良い結果が得られます。あなただけがこの決定を下すことができます (私はあなたのコードベースを見ることができません) が、いくつかのリファクタリングが間違いなく役に立ちます。

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

于 2008-09-14T05:55:21.417 に答える
5

この問題に遭遇したとき、私は通常、あなたが説明したのと同じことを行いますが、静的メソッドを保護して手動で呼び出すことができるようにします。これに加えて、メソッドが問題なく複数回呼び出されることを確認します (それ以外の場合は、テストに関する限り、静的初期化子よりも優れているわけではありません)。

これはかなりうまく機能し、静的イニシャライザ メソッドが期待/実行したいことを実際にテストできます。場合によっては、静的な初期化コードを用意するのが最も簡単な場合もありますが、それを置き換えるために過度に複雑なシステムを構築する価値はありません。

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

于 2008-09-14T09:17:33.067 に答える
3

Groovy でテスト コードを記述し、メタプログラミングを使用して静的メソッドを簡単にモックできます。

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

Math.max 1, 2

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

敬具

于 2008-09-14T07:42:58.100 に答える
1

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

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);
}

それぞれ。

于 2008-09-14T14:55:56.250 に答える
1

静的初期化子の代わりに、ある種のファクトリが本当に必要だと思います。

シングルトンと抽象ファクトリを組み合わせることで、現在と同じ機能を実現でき、テスト容易性も向上しますが、定型コードが大量に追加されるため、リファクタリングを試みたほうがよい場合があります。静的なものを完全に取り除くか、少なくとも複雑ではない解決策で逃げることができれば。

ただし、コードを見ずにそれが可能かどうかを判断するのは困難です。

于 2008-09-14T07:12:20.810 に答える