19

レガシーコードの単体テストを作成しようとしています。私がテストしているクラスには、いくつかの静的変数があります。私のテストケースクラスにはいくつかの@Testメソッドがあります。したがって、それらはすべて同じ状態を共有します。

テスト間ですべての静的変数をリセットする方法はありますか?

私が思いついた解決策の1つは、各フィールドを明示的にリセットすることです。例:

field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();

ご覧のとおり、各変数にはカスタムの再初期化が必要です。このアプローチは拡張が容易ではなく、レガシーコードベースにはそのようなクラスがたくさんあります。すべてを一度にリセットする方法はありますか?たぶん、毎回クラスをリロードすることによって?

考えられる良い解決策として、powermockのようなものを使用し、テストごとに個別のクラスローダーを作成することだと思います。しかし、私はそれを行う簡単な方法を見ていません。

4

3 に答える 3

27

わかりました、私はそれを理解したと思います。とても簡単です。

@PrepareForTestpowermockのアノテーションをメソッドレベルに移動することができます。この場合、powermockはメソッドごとにクラスローダーを作成します。だからそれは私が必要とすることをします。

于 2012-08-06T15:12:36.023 に答える
3

このクラスを含むコードをテストしているとしましょう。

import java.math.BigInteger;
import java.util.HashSet;

public class MyClass {
  static int someStaticField = 5;
  static BigInteger anotherStaticField = BigInteger.ONE;
  static HashSet<Integer> mutableStaticField = new HashSet<Integer>();
}

Javaのリフレクション機能を使用して、プログラムですべての静的フィールドをリセットできます。テストを開始する前にすべての初期値を保存する必要があります。その後、各テストを実行する前にそれらの値をリセットする必要があります。JUnitには、このためにうまく機能するアノテーションが@BeforeClassあります。@Before簡単な例を次に示します。

import static org.junit.Assert.*;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Map;
import java.util.HashMap;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class MyTest extends Object {

  static Class<?> staticClass = MyClass.class;
  static Map<Field,Object> defaultFieldVals = new HashMap<Field,Object>();

  static Object tryClone(Object v) throws Exception {
    if (v instanceof Cloneable) {
      return v.getClass().getMethod("clone").invoke(v);
    }
    return v;
  }

  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    Field[] allFields = staticClass.getDeclaredFields();
    try {
      for (Field field : allFields) {
          if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
              Object value = tryClone(field.get(null));
              defaultFieldVals.put(field, value);
          }
      }
    }
    catch (IllegalAccessException e) {
      System.err.println(e);
      System.exit(1);
    }
  }

  @AfterClass
  public static void tearDownAfterClass() {
    defaultFieldVals = null;
  }

  @Before
  public void setUp() throws Exception {
    // Reset all static fields
    for (Map.Entry<Field, Object> entry : defaultFieldVals.entrySet()) {
      Field field = entry.getKey();
      Object value = entry.getValue();
      Class<?> type = field.getType();
      // Primitive types
      if (type == Integer.TYPE) {
        field.setInt(null, (Integer) value);
      }
      // ... all other primitive types need to be handled similarly
      // All object types
      else {
        field.set(null, tryClone(value));
      }
    }
  }

  private void testBody() {
    assertTrue(MyClass.someStaticField == 5);
    assertTrue(MyClass.anotherStaticField == BigInteger.ONE);
    assertTrue(MyClass.mutableStaticField.isEmpty());
    MyClass.someStaticField++;
    MyClass.anotherStaticField = BigInteger.TEN;
    MyClass.mutableStaticField.add(1);
    assertTrue(MyClass.someStaticField == 6);
    assertTrue(MyClass.anotherStaticField.equals(BigInteger.TEN));
    assertTrue(MyClass.mutableStaticField.contains(1));
  }

  @Test
  public void test1() {
    testBody();
  }

  @Test
  public void test2() {
    testBody();
  }

}

のコメントで述べたように、 ssetUp()を処理するには、残りのプリミティブ型を同様のコードで処理する必要がありますint。すべてのラッパークラスには、と同じようにチェックできるTYPEフィールド(egDouble.TYPEと)があります。フィールドの型がプリミティブ型(プリミティブ配列を含む)の1つでない場合、それはであり、ジェネリックとして処理できます。Character.TYPEInteger.TYPEObjectObject

final、、、privateおよびフィールドを処理するためにコードを微調整する必要があるかもしれませんが、ドキュメントprotectedからそれを行う方法を理解できるはずです。

レガシーコードで頑張ってください!

編集:

静的フィールドの1つに格納されている初期値が変更された場合、変更されたオブジェクトが再割り当てされるだけなので、キャッシュして復元するだけではうまくいきません。また、このコードを拡張して、単一のクラスではなく静的クラスの配列を操作できると想定しています。

編集:

Cloneableあなたの例のようなケースを処理するためのオブジェクトのチェックを追加しましたHashMap。明らかにそれは完璧ではありませんが、うまくいけば、これはあなたが遭遇するほとんどのケースをカバーするでしょう。うまくいけば、手動でリセットする(つまり、リセットコードをsetUp()メソッドに追加する)のにそれほど大きな苦痛がないほど十分なエッジケースがほとんどありません。

于 2012-08-06T14:59:06.227 に答える
0

これが私の2セントです

1.静的参照をゲッター/セッターに抽出します

これは、そのサブクラスを作成できる場合に機能します。

public class LegacyCode {
  private static Map<String, Object> something = new HashMap<String, Object>();

  public void doSomethingWithMap() {

    Object a = something.get("Object")
    ...
    // do something with a
    ...
    something.put("Object", a);
  }
}

着替える

public class LegacyCode {
  private static Map<String, Object> something = new HashMap<String, Object>();

  public void doSomethingWithMap() {

    Object a = getFromMap("Object");
    ...
    // do something with a
    ...
    setMap("Object", a);
  }

  protected Object getFromMap(String key) {
    return something.get(key);
  }

  protected void setMap(String key, Object value) {
    seomthing.put(key, value);
  }
}

次に、サブクラス化することで依存関係を取り除くことができます。

public class TestableLegacyCode extends LegacyCode {
  private Map<String, Object> map = new HashMap<String, Object>();

  protected Object getFromMap(String key) {
    return map.get(key);
  }

  protected void setMap(String key, Object value) {
    map.put(key, value);
  }
}

2.静的セッターを導入します

これはかなり明白なはずです。

public class LegacyCode {
  private static Map<String, Object> something = new HashMap<String, Object>();

  public static setSomethingForTesting(Map<String, Object> somethingForTest) {
    something = somethingForTest;
  }

  ....
}

どちらの方法もきれいではありませんが、テストが終わったらいつでも後で戻ることができます。

于 2012-08-06T16:50:01.497 に答える