java - udemy - Restablecer la variable estática de clase durante la prueba unitaria
software para pruebas automatizadas (3)
Aquí están mis dos centavos
1. Extraer la referencia estática en getters / setters
Esto funciona cuando puedes crear una subclase de él.
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);
}
}
cambie 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);
}
}
entonces puedes deshacerte de la dependencia por subclase.
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 el setter estático
Este debería ser bastante obvio.
public class LegacyCode {
private static Map<String, Object> something = new HashMap<String, Object>();
public static setSomethingForTesting(Map<String, Object> somethingForTest) {
something = somethingForTest;
}
....
}
Ambas formas no son bonitas, pero siempre podemos volver más tarde una vez que tengamos pruebas.
Estoy tratando de escribir una prueba de unidad para un código heredado . La clase que estoy probando tiene varias variables estáticas. Mi clase de caso de prueba tiene algunos métodos @Test
. De ahí que todos ellos compartan el mismo estado.
¿Hay manera de restablecer todas las variables estáticas entre las pruebas?
Una solución que encontré es restablecer explícitamente cada campo, por ejemplo:
field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();
Como ves, cada variable necesita reinicialización personalizada. El enfoque no es fácil de escalar, hay muchas clases de este tipo en la base de código heredado. ¿Hay alguna manera de restablecer todo a la vez? ¿Tal vez recargando la clase cada vez?
Creo que como una buena solución posible es usar algo como powermock y crear un cargador de clases por separado para cada prueba. Pero no veo la manera fácil de hacerlo.
Digamos que estoy probando un código que involucra a esta clase:
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>();
}
Puede restablecer todos los campos estáticos mediante programación utilizando las capacidades de reflexión de Java. Deberá almacenar todos los valores iniciales antes de comenzar la prueba, y luego deberá restablecer esos valores antes de ejecutar cada prueba. JUnit tiene @BeforeClass
y @Before
anotaciones que funcionan muy bien para esto. Aquí hay un ejemplo simple:
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();
}
}
Como señalé en los comentarios en setUp()
, necesitarás manejar el resto de los tipos primitivos con un código similar para que manejen int
s. Todas las clases de envoltorio tienen un campo TYPE
(por ejemplo, Double.TYPE
y Character.TYPE
) que puede marcar como Integer.TYPE
. Si el tipo de campo no es uno de los tipos primitivos (incluidas las matrices primitivas), entonces es un Object
y puede manejarse como un Object
genérico.
Es posible que deba modificarse el código para manejar los campos final
, private
y protected
, pero debe poder averiguar cómo hacerlo a partir de la documentación .
¡Buena suerte con tu código legado!
Editar:
Olvidé mencionar, si el valor inicial almacenado en uno de los campos estáticos está mutado, entonces simplemente almacenarlo en caché y restaurarlo no funcionará, ya que simplemente volverá a asignar el objeto mutado. También asumo que podrás expandir este código para trabajar con una serie de clases estáticas en lugar de una sola clase.
Editar:
Cloneable
un cheque para los objetos Cloneable
para manejar casos como el HashMap
en su ejemplo. Obviamente no es perfecto, pero espero que esto cubra la mayoría de los casos en los que se encontrará. Es de esperar que haya pocos casos de borde que no sea tan setUp()
reiniciarlos manualmente (es decir, agregue el código de reinicio al método setUp()
).
Ok, creo que lo descubrí. Es muy simple.
Es posible mover la anotación de @PrepareForTest
@PrepareForTest al nivel del método. En este caso, powermock crea un cargador de clases por método. Así hace lo que necesito.