java - tutorial - pruebas unitarias pdf
¿Usar diferentes clasificadores para diferentes pruebas JUnit? (5)
Tengo un objeto Singleton / Factory para el que me gustaría escribir una prueba JUnit. El método Factory decide qué clase de implementación instanciar en función de un nombre de clase en un archivo de propiedades en el classpath. Si no se encuentra ningún archivo de propiedades, o si el archivo de propiedades no contiene la clave del nombre de clase, la clase crea una instancia de una clase de implementación predeterminada.
Como la fábrica conserva una instancia estática de Singleton para usar una vez que se ha instanciado, para poder probar la lógica de "conmutación por error" en el método Factory, necesitaría ejecutar cada método de prueba en un cargador de clases diferente.
¿Hay alguna forma con JUnit (o con otro paquete de prueba de unidades) para hacer esto?
editar: aquí está el código de Factory que está en uso:
private static MyClass myClassImpl = instantiateMyClass();
private static MyClass instantiateMyClass() {
MyClass newMyClass = null;
String className = null;
try {
Properties props = getProperties();
className = props.getProperty(PROPERTY_CLASSNAME_KEY);
if (className == null) {
log.warn("instantiateMyClass: Property [" + PROPERTY_CLASSNAME_KEY
+ "] not found in properties, using default MyClass class [" + DEFAULT_CLASSNAME + "]");
className = DEFAULT_CLASSNAME;
}
Class MyClassClass = Class.forName(className);
Object MyClassObj = MyClassClass.newInstance();
if (MyClassObj instanceof MyClass) {
newMyClass = (MyClass) MyClassObj;
}
}
catch (...) {
...
}
return newMyClass;
}
private static Properties getProperties() throws IOException {
Properties props = new Properties();
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_FILENAME);
if (stream != null) {
props.load(stream);
}
else {
log.error("getProperties: could not load properties file [" + PROPERTIES_FILENAME + "] from classpath, file not found");
}
return props;
}
A continuación puede encontrar una muestra que no necesita un corredor de prueba JUnit por separado y funciona también con trucos de carga de clase como Mockito.
package com.mycompany.app;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.net.URLClassLoader;
import org.junit.Test;
public class ApplicationInSeparateClassLoaderTest {
@Test
public void testApplicationInSeparateClassLoader1() throws Exception {
testApplicationInSeparateClassLoader();
}
@Test
public void testApplicationInSeparateClassLoader2() throws Exception {
testApplicationInSeparateClassLoader();
}
private void testApplicationInSeparateClassLoader() throws Exception {
//run application code in separate class loader in order to isolate static state between test runs
Runnable runnable = mock(Runnable.class);
//set up your mock object expectations here, if needed
InterfaceToApplicationDependentCode tester = makeCodeToRunInSeparateClassLoader(
"com.mycompany.app", InterfaceToApplicationDependentCode.class, CodeToRunInApplicationClassLoader.class);
//if you want to try the code without class loader isolation, comment out above line and comment in the line below
//CodeToRunInApplicationClassLoader tester = new CodeToRunInApplicationClassLoaderImpl();
tester.testTheCode(runnable);
verify(runnable).run();
assertEquals("should be one invocation!", 1, tester.getNumOfInvocations());
}
/**
* Create a new class loader for loading application-dependent code and return an instance of that.
*/
@SuppressWarnings("unchecked")
private <I, T> I makeCodeToRunInSeparateClassLoader(
String packageName, Class<I> testCodeInterfaceClass, Class<T> testCodeImplClass) throws Exception {
TestApplicationClassLoader cl = new TestApplicationClassLoader(
packageName, getClass(), testCodeInterfaceClass);
Class<?> testerClass = cl.loadClass(testCodeImplClass.getName());
return (I) testerClass.newInstance();
}
/**
* Bridge interface, implemented by code that should be run in application class loader.
* This interface is loaded by the same class loader as the unit test class, so
* we can call the application-dependent code without need for reflection.
*/
public static interface InterfaceToApplicationDependentCode {
void testTheCode(Runnable run);
int getNumOfInvocations();
}
/**
* Test-specific code to call application-dependent code. This class is loaded by
* the same class loader as the application code.
*/
public static class CodeToRunInApplicationClassLoader implements InterfaceToApplicationDependentCode {
private static int numOfInvocations = 0;
@Override
public void testTheCode(Runnable runnable) {
numOfInvocations++;
runnable.run();
}
@Override
public int getNumOfInvocations() {
return numOfInvocations;
}
}
/**
* Loads application classes in separate class loader from test classes.
*/
private static class TestApplicationClassLoader extends URLClassLoader {
private final String appPackage;
private final String mainTestClassName;
private final String[] testSupportClassNames;
public TestApplicationClassLoader(String appPackage, Class<?> mainTestClass, Class<?>... testSupportClasses) {
super(((URLClassLoader) getSystemClassLoader()).getURLs());
this.appPackage = appPackage;
this.mainTestClassName = mainTestClass.getName();
this.testSupportClassNames = convertClassesToStrings(testSupportClasses);
}
private String[] convertClassesToStrings(Class<?>[] classes) {
String[] results = new String[classes.length];
for (int i = 0; i < classes.length; i++) {
results[i] = classes[i].getName();
}
return results;
}
@Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
if (isApplicationClass(className)) {
//look for class only in local class loader
return super.findClass(className);
}
//look for class in parent class loader first and only then in local class loader
return super.loadClass(className);
}
private boolean isApplicationClass(String className) {
if (mainTestClassName.equals(className)) {
return false;
}
for (int i = 0; i < testSupportClassNames.length; i++) {
if (testSupportClassNames[i].equals(className)) {
return false;
}
}
return className.startsWith(appPackage);
}
}
}
Cuando me encuentro con este tipo de situaciones, prefiero usar lo que es un truco. En su lugar, podría exponer un método protegido como reiniciar (), y luego invocarlo desde la prueba para establecer de manera efectiva la fábrica a su estado inicial. Este método solo existe para los casos de prueba, y lo documentamos como tal.
Es un truco, pero es mucho más fácil que otras opciones y no necesitará una libra de terceros para hacerlo (aunque si prefiere una solución más limpia, es probable que haya algún tipo de herramientas de terceros por ahí). podría utilizar).
Esta pregunta puede ser antigua, pero como esta era la respuesta más cercana que encontré cuando tuve este problema, pensé que describiría mi solución.
Usando JUnit 4
Divida sus pruebas de modo que haya un método de prueba por clase (esta solución solo cambia los clasificadores entre clases, no entre métodos ya que el padre padre reúne todos los métodos una vez por clase)
Agregue la @RunWith(SeparateClassloaderTestRunner.class)
a sus clases de prueba.
Cree el SeparateClassloaderTestRunner
para que se vea así:
public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner {
public SeparateClassloaderTestRunner(Class<?> clazz) throws InitializationError {
super(getFromTestClassloader(clazz));
}
private static Class<?> getFromTestClassloader(Class<?> clazz) throws InitializationError {
try {
ClassLoader testClassLoader = new TestClassLoader();
return Class.forName(clazz.getName(), true, testClassLoader);
} catch (ClassNotFoundException e) {
throw new InitializationError(e);
}
}
public static class TestClassLoader extends URLClassLoader {
public TestClassLoader() {
super(((URLClassLoader)getSystemClassLoader()).getURLs());
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("org.mypackages.")) {
return super.findClass(name);
}
return super.loadClass(name);
}
}
}
Tenga en cuenta que tuve que hacer esto para probar el código que se ejecuta en un marco heredado que no pude cambiar. Dada la opción, reduciría el uso de estática y / o pondría ganchos de prueba para permitir que el sistema se reinicie. Puede que no sea lindo, pero me permite probar una gran cantidad de código que de otra forma sería difícil.
Además, esta solución rompe cualquier otra cosa que se base en trucos de carga de clases como Mockito.
Puede usar Reflection para establecer myClassImpl
llamando nuevamente a instantiateMyClass()
. Mire esta respuesta para ver ejemplos de patrones para jugar con métodos privados y variables.
Si ejecuta Junit a través de la tarea Ant , puede configurar fork=true
para ejecutar cada clase de pruebas en su propia JVM. Además, coloque cada método de prueba en su propia clase y cada uno cargará e inicializará su propia versión de MyClass
. Es extremo pero muy efectivo.