我正在尝试为遗留代码编写单元测试。我正在测试的类有几个静态变量。我的测试用例类有几个@Test
方法。因此,他们都处于相同的状态。
有没有办法在测试之间重置所有静态变量?
我提出的一个解决方案是明确重置每个字段,例如:
field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();
如您所见,每个变量都需要自定义重新初始化。这种方法不容易扩展,遗留代码库中有很多这样的类。有没有办法一次性重置所有内容?也许每次重新上课?
作为一种可能的好解决方案,我认为是使用类似powermock的东西,并为每个测试创建一个单独的类加载器。但我认为没有简单的方法可以做到。
答案 0 :(得分:22)
好的,我想我已经明白了。这很简单。
可以将@PrepareForTest
powermock的注释移动到方法级别。在这种情况下,powermock为每个方法创建类加载器。所以我需要这样做。
答案 1 :(得分: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();
}
}
正如我在setUp()
中的注释中所指出的那样,您需要处理其余的基本类型,并使用类似的代码来处理int
。所有包装类都有TYPE
字段(例如Double.TYPE
和Character.TYPE
),您可以像Integer.TYPE
一样进行检查。如果字段的类型不是基本类型之一(包括基本数组),则它是Object
,可以作为通用Object
处理。
可能需要调整代码来处理final
,private
和protected
字段,但您应该能够从the documentation计算出如何执行此操作。
祝你的遗产代码好运!
修改强> 的
我忘了提一下,如果存储在其中一个静态字段中的初始值发生了变异,那么只需缓存它并恢复它就不会有效,因为它只会重新分配变异对象。我还假设您将能够扩展此代码以使用静态类而不是单个类。
修改强> 的
我添加了对Cloneable
个对象的检查,以处理示例中HashMap
之类的情况。显然它并不完美,但希望这将涵盖你将遇到的大多数情况。希望有足够的边缘情况,手动重置它们不会太大(即将重置代码添加到setUp()
方法)。
答案 2 :(得分:0)
这是我的两分钱
当你能够创建它的子类时,这是有效的。
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;
}
....
}
这两种方式都不是很好,但是一旦我们进行测试,我们总是可以回来。