レガシーコードの単体テストを作成しようとしています。私がテストしているクラスには、いくつかの静的変数があります。私のテストケースクラスにはいくつかの@Test
メソッド。したがって、それらはすべて同じ状態を共有します。
テスト間ですべての静的変数をリセットする方法はありますか?
私が思いついた解決策の1つは、各フィールドを明示的にリセットすることです。例:
field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();
ご覧のとおり、各変数にはカスタムの再初期化が必要です。このアプローチは拡張が容易ではなく、レガシーコードベースにはそのようなクラスがたくさんあります。すべてを一度にリセットする方法はありますか?たぶん毎回クラスをリロードすることによって?
考えられる良い解決策として、powermockのようなものを使用し、テストごとに個別のクラスローダーを作成することだと思います。しかし、私はそれを行う簡単な方法がわかりません。
わかりました、私はそれを理解したと思います。とても簡単です。
@PrepareForTest
powermockのアノテーションをメソッドレベルに移動することができます。この場合、powermockはメソッドごとにクラスローダーを作成します。だから私が必要なことをします。
このクラスを含むいくつかのコードをテストしているとしましょう:
_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();
}
}
_
setUp()
のコメントで述べたように、int
sを処理するには、同様のコードで残りのプリミティブ型を処理する必要があります。すべてのラッパークラスにはTYPE
フィールド(例:_Double.TYPE
_および_Character.TYPE
_)があり、_Integer.TYPE
_と同じように確認できます。フィールドの型がプリミティブ型(プリミティブ配列を含む)の1つでない場合、それはObject
であり、ジェネリックObject
として処理できます。
final
、private
、およびprotected
フィールドを処理するためにコードを微調整する必要があるかもしれませんが、それを行う方法を ドキュメント 。
レガシーコードで頑張ってください!
編集:
静的フィールドの1つに格納されている初期値が変更されている場合、変更されたオブジェクトを再割り当てするだけなので、キャッシュして復元するだけではうまくいきません。また、このコードを拡張して、単一のクラスではなく静的クラスの配列を操作できると想定しています。
編集:
例のCloneable
のようなケースを処理するために、HashMap
オブジェクトのチェックを追加しました。明らかにそれは完璧ではありませんが、うまくいけば、これはあなたが遭遇するほとんどのケースをカバーするでしょう。うまくいけば、手動でリセットするのにそれほど大きな苦痛にならないほど十分なエッジケースがあります(つまり、リセットコードをsetUp()
メソッドに追加します)。
これが私の2セントです
これは、そのサブクラスを作成できる場合に機能します。
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);
}
}
これはかなり明白なはずです。
public class LegacyCode {
private static Map<String, Object> something = new HashMap<String, Object>();
public static setSomethingForTesting(Map<String, Object> somethingForTest) {
something = somethingForTest;
}
....
}
どちらの方法もきれいではありませんが、テストが済んだらいつでも後で戻ることができます。