[英]Reset class static variable during unit test
我正在嘗試為遺留代碼編寫單元測試。 我正在測試的類有幾個靜態變量。 我的測試用例類有一些@Test
方法。 因此,他們都擁有相同的狀態。
有沒有辦法在測試之間重置所有靜態變量?
我提出的一個解決方案是明確重置每個字段,例如:
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
。 所有包裝類都有一個TYPE
字段(例如Double.TYPE
和Character.TYPE
),您可以像Integer.TYPE
一樣檢查它。 如果字段的類型不是基本類型之一(包括基本數組),那么它是一個Object
,可以作為通用Object
處理。
可能需要調整代碼以處理final
, private
和protected
字段,但您應該能夠從文檔中了解如何執行此操作。
祝你的遺產代碼好運!
編輯:
我忘了提一下,如果存儲在其中一個靜態字段中的初始值發生了變異,那么只需緩存它並恢復它就不會有效,因為它只會重新分配變異對象。 我還假設您將能夠擴展此代碼以使用靜態類數組而不是單個類。
編輯:
我已經添加了一個Cloneable
對象的檢查來處理你的例子中的HashMap
類的情況。 顯然它並不完美,但希望這將涵蓋你將遇到的大多數情況。 希望沒有足夠的邊緣情況,手動重置它們不會太大(即將重置代碼添加到setUp()
方法)。
這是我的兩分錢
當您能夠創建它的子類時,這可以工作。
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;
}
....
}
兩種方式都不漂亮,但是一旦我們進行測試,我們總能回來。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.