Ответ 1
Хорошо, думаю, я понял это. Это очень просто.
Можно переместить аннотацию powermock @PrepareForTest
на уровень метода. В этом случае powermock создает classloader для каждого метода. Поэтому мне это нужно.
Я пытаюсь написать unit test для устаревшего кода. Класс, который я тестирую, имеет несколько статических переменных. Класс моего тестового примера имеет несколько методов @Test
. Следовательно, все они имеют одно и то же состояние.
Есть ли способ reset всех статических переменных между тестами?
Одно из решений, которое я выбрал, - это явно reset каждое поле, например:
field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();
Как вы видите, каждая переменная нуждается в пользовательской повторной инициализации. Этот подход нелегко масштабировать, в базе устаревших кодов много таких классов. Есть ли способ reset все сразу? Может быть, перезагружая класс каждый раз?
Как возможное хорошее решение, я думаю, это использовать что-то вроде powermock и создать отдельный загрузчик классов для каждого теста. Но я не вижу простого способа сделать это.
Хорошо, думаю, я понял это. Это очень просто.
Можно переместить аннотацию powermock @PrepareForTest
на уровень метода. В этом случае powermock создает classloader для каждого метода. Поэтому мне это нужно.
Скажем, я тестирую код с этим классом:
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>();
}
Вы можете reset все статические поля программно использовать возможности отражения Java. Перед началом теста вам нужно будет сохранить все начальные значения, а затем вам нужно будет reset те значения перед каждым тестом. 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
. Если тип поля не является одним из примитивных типов (включая примитивные массивы), то он Object
и может обрабатываться как общий Object
.
Возможно, потребуется изменить код для обработки полей final
, private
и protected
, но вы должны понять, как это сделать из .
Удачи вам в устаревшем коде!
Edit:
Я забыл упомянуть, если начальное значение, хранящееся в одном из статических полей, мутировано, то просто кэшировать его и восстановить его не будет трюк, так как он просто повторно назначит мутированный объект. Я также предполагаю, что вы сможете расширить этот код, чтобы работать с массивом статических классов, а не с одним классом.
Edit:
Я добавил проверку для объектов Cloneable
для обработки таких случаев, как HashMap
в вашем примере. Очевидно, что это не идеально, но, надеюсь, это будет охватывать большинство случаев, в которые вы столкнетесь. Хотелось бы надеяться, что достаточно кратных случаев, что он не будет слишком большим для боли до reset их вручную (т.е. Добавит код reset к методу 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;
}
....
}
Оба способа не очень хороши, но мы всегда можем вернуться позже, когда у нас есть тесты.