java 在单元测试期间重置类静态变量
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11829817/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reset class static variable during unit test
提问by kan
I am trying to write a unit test for a legacy code. The class which I'm testing has several static variables. My test case class has a few @Test
methods. Hence all of them share the same state.
我正在尝试为遗留代码编写单元测试。我正在测试的类有几个静态变量。我的测试用例类有几个@Test
方法。因此,它们都共享相同的状态。
Is there way to reset all static variables between tests?
有没有办法在测试之间重置所有静态变量?
One solution I came up is to explicitly reset each field, e.g.:
我提出的一个解决方案是明确重置每个字段,例如:
field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();
As you see, each variable needs custom re-initialization. The approach is not easy to scale, there are a lot such classes in the legacy code base. Is there any way to reset everything at once? Maybe by reloading the class each time?
如您所见,每个变量都需要自定义重新初始化。这种方法不容易扩展,遗留代码库中有很多这样的类。有没有办法一次重置所有内容?也许通过每次重新加载课程?
As a possible good solution I think is to use something like powermock and create a separate classloader for each test. But I don't see easy way to do it.
作为一个可能的好解决方案,我认为是使用类似 powermock 的东西并为每个测试创建一个单独的类加载器。但我看不到简单的方法来做到这一点。
回答by kan
Ok, I think I figured it out. It is very simple.
好吧,我想我想通了。这很简单。
It is possible to move @PrepareForTest
powermock's annotation to the method level. In this case powermock creates classloader per method. So it does that I need.
可以将@PrepareForTest
powermock 的注解移动到方法级别。在这种情况下,powermock 会为每个方法创建类加载器。所以它满足了我的需求。
回答by DaoWen
Let's say I'm testing some code involving this class:
假设我正在测试一些涉及此类的代码:
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>();
}
You can reset all of the static fields programmatically using Java's reflection capabilities. You will need to store all of the initial values before you begin the test, and then you'll need to reset those values before each test is run. JUnit has @BeforeClass
and @Before
annotations that work nicely for this. Here's a simple example:
您可以使用 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();
}
}
As I noted in the comments in setUp()
, you'll need to handle the rest of the primitive types with similar code for that to handle int
s. All of the wrapper classes have a TYPE
field (e.g. Double.TYPE
and Character.TYPE
) which you can check just like Integer.TYPE
. If the field's type isn't one of the primitive types (including primitive arrays) then it's an Object
and can be handled as a generic Object
.
正如我在 中的评论中所指出的setUp()
,您需要使用类似的代码来处理其余的原始类型来处理int
s。所有的包装类都有一个TYPE
字段(例如Double.TYPE
和Character.TYPE
),您可以像 一样检查它Integer.TYPE
。如果字段的类型不是原始类型之一(包括原始数组),则它是 anObject
并且可以作为泛型处理Object
。
The code might need to be tweaked to handle final
, private
, and protected
fields, but you should be able to figure how to do that from the documentation.
可能需要调整代码以处理final
、private
和protected
字段,但您应该能够从文档中弄清楚如何做到这一点。
Good luck with your legacy code!
祝您的遗留代码好运!
Edit:
编辑:
I forgot to mention, if the initial value stored in one of the static fields is mutated then simply caching it and restoring it won't do the trick since it will just re-assign the mutated object. I'm also assuming that you'll be able to expand on this code to work with an array of static classes rather than a single class.
我忘了提到,如果存储在其中一个静态字段中的初始值发生了变异,那么简单地缓存它并恢复它是行不通的,因为它只会重新分配变异的对象。我还假设您将能够扩展此代码以使用静态类数组而不是单个类。
Edit:
编辑:
I've added a check for Cloneable
objects to handle cases like the HashMap
in your example. Obviously it's not perfect, but hopefully this will cover most of the cases you'll run in to. Hopefully there are few enough edge cases that it won't be too big of a pain to reset them by hand (i.e. add the reset code to the setUp()
method).
我添加了一个Cloneable
对象检查来处理HashMap
你的例子中的情况。显然它并不完美,但希望这将涵盖您将遇到的大多数情况。希望很少有足够的边缘情况,手动重置它们不会太痛苦(即将重置代码添加到setUp()
方法中)。
回答by Rangi Lin
Here's my two cents
这是我的两分钱
1. Extract static reference into getters / setters
1. 将静态引用提取到 getter/setter 中
This works when you are able to create a subclass of it.
当您能够创建它的子类时,这会起作用。
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);
}
}
change into
变成
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);
}
}
then you can get rid of dependency by subclass it.
然后你可以通过继承它来摆脱依赖。
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. Introduce static setter
2.引入静态setter
This one should be pretty obvious.
这个应该很明显了。
public class LegacyCode {
private static Map<String, Object> something = new HashMap<String, Object>();
public static setSomethingForTesting(Map<String, Object> somethingForTest) {
something = somethingForTest;
}
....
}
Both ways are not pretty, but we can always come back later once we have tests.
两种方式都不是很好,但是一旦我们进行了测试,我们总是可以稍后再回来。