Java: ¿cómo intercambiar una ruta de archivo de recursos por un archivo de prueba durante las pruebas unitarias?

8

Tengo un archivo de recursos que tiene algunas configuraciones. Tengo una clase ResourceLoader que carga la configuración de este archivo. Esta clase es actualmente una clase singleton instanciada con entusiasmo. Tan pronto como se carga esta clase, lee la configuración del archivo (la ruta del archivo se almacena como un campo constante en otra clase). Algunos de estos ajustes no son adecuados para pruebas unitarias. Por ejemplo, tengo tiempo de suspensión de hebras en este archivo, que puede ser horas para el código de producción, pero me gustaría que sea un par de milisegundos para las pruebas unitarias. Entonces tengo otro archivo de recursos de prueba que tiene un conjunto diferente de valores. Mi pregunta es ¿cómo hago para intercambiar el archivo de recursos principal con este archivo de prueba durante las pruebas unitarias? El proyecto es un proyecto maven y estoy usando testng como marco de prueba. Estos son algunos de los enfoques que yo '

  1. Use @BeforeSuite y modifique la variable constante FilePath para apuntar al archivo de prueba y use @AfterSuite para apuntarlo nuevamente al archivo original. Esto parece estar funcionando, pero creo que debido a que la clase ResourceLoader se instancia con entusiasmo, no hay garantía de que el método @BeforeSuite siempre se ejecute antes de que se cargue la clase ResourceLoader y, por lo tanto, las propiedades antiguas se pueden cargar antes de cambiar la ruta del archivo. Aunque la mayoría de los compiladores cargan una clase solo cuando se requiere, no estoy seguro de si este es un requisito de especificación de Java. Entonces, en teoría, esto puede no funcionar para todos los compiladores de Java.

  2. Pase la ruta del archivo de recursos como un argumento de línea de comando. Puedo agregar la ruta del archivo de recursos de prueba como argumento de línea de comando en la configuración segura en el pom. Esto parece un poco excesivo.

  3. Use el enfoque en 1. y haga que ResourceLoader sea perezoso instanciado. Esto garantiza que si se llama a @BeforeMethod antes de la primera llamada a ResourceLoader.getInstance (). GetProperty (..), ResourceLoader cargará el archivo correcto. Esto parece ser mejor que los primeros 2 enfoques, pero creo que crear una instancia de clase única de perezoso hace que sea feo, ya que no puedo usar un patrón simple como convertirlo en una enumeración y tal (como es el caso de la instanciación ansiosa).

Esto parece un escenario común, ¿cuál es la forma más común de hacerlo?

Marko Cain
fuente
¿Está reelaborando su aplicación para usar una mejor biblioteca de configuración una opción?
Thorbjørn Ravn Andersen
Sí, solo quiero saber la forma estándar de hacerlo. Estoy abierto a reelaborar la aplicación.
Marko Cain
La única forma estándar es utilizar las propiedades del sistema y / o el entorno. Eso suele ser demasiado limitante porque son difíciles de proporcionar y anular desde la línea de comandos, por ejemplo, en las pruebas. Le sugiero que agregue información a su pregunta sobre lo que necesita y cómo espera poder configurar su aplicación con algunos casos de uso. Es posible que desee pensar en pasar los valores de configuración en el constructor de las clases que los necesitan.
Thorbjørn Ravn Andersen

Respuestas:

7

Todos los singletons instanciados ansiosamente o perezosamente son antipatrones . El uso de singletons hace que las pruebas unitarias sean más difíciles porque no hay una manera fácil de burlarse de singletons.

Método estático simulado

Una solución alternativa es usar PowerMock para burlarse del método estático que devuelve una instancia única.

Usar inyección de dependencia

Una mejor solución es usar la inyección de dependencia. Si ya usa un marco de inyección de dependencias (por ejemplo, Spring, CDI), refactorice el código para hacer ResourceLoaderun bean administrado con alcance singleton .

Si no utiliza un marco de inyección de dependencias, una refactorización fácil será realizar cambios en todas las clases utilizando el singleton ResourceLoader:

public class MyService {

  public MyService() {
    this(ResourceLoader.getInstance());
  }

  public MyService(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
}

Y luego en pruebas unitarias simulacro ResourceLoaderusando Mockito

ResourceLoader resourceLoader = mock(ResourceLoader.class);
when(ResourceLoader.getProperty("my-property")).thenReturn("10");
MyService myService = new MyService(resourceLoader);

Configuración de externalización

Otro enfoque es colocar un archivo con la configuración de prueba debajo src/test/resources. Si almacena la configuración en src/main/resources/application.properties, un archivo la src/test/resources/application.propertiesanulará.

Además, es una buena idea externalizar la configuración a un archivo no empaquetado en un JAR. De esta manera, el archivo src/main/resources/application.propertiescontendrá las propiedades predeterminadas y un archivo pasado usando el parámetro de línea de comandos anulará estas propiedades. Por lo tanto, un archivo con propiedades de prueba también se pasará como un parámetro de línea de comando. Vea cómo Spring maneja la configuración externalizada .

Usar las propiedades del sistema Java

Un enfoque aún más sencillo es permitir la anulación de las propiedades predeterminadas con las Propiedades del sistema en el método ResourceLoader.getInstance().getProperty()y pasar las propiedades de prueba de esta manera

public String getProperty(String name) {
  // defaultProperties are loaded from a file on a file system:
  // defaultProperties.load(new FileInputStream(new File(filePath)));
  // or from a file in the classpath:
  // defaultProperties.load(ResourceLoader.class.getResourceAsStream(filePath));
  return System.getProperty(name, defaultProperties.get(name));
}
Evgeniy Khyst
fuente
0

Comprueba si estás en jUnit

También puede verificar en tiempo de ejecución si jUnit se está ejecutando y luego intercambiar la ruta. Eso funcionaría así ( no probado ):

public static funktionToTest() {
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
    List<StackTraceElement> list = Arrays.asList(stackTraceElements);
    String path = "/origin/path/to/your/file"; // here can you set the default path to your rescource
    for (StackTraceElement stackTraceElement : list) {
        if (stackTraceElement.getClassName().startsWith("org.junit.")) {
            path = "/path/only/in/jUnit/test"; // and here the normal path
        }           
    }
    //do what you want with the path
}
scolastico
fuente