¿Cómo probar el código que depende de las variables de entorno usando JUnit?

140

Tengo un fragmento de código Java que utiliza una variable de entorno y el comportamiento del código depende del valor de esta variable. Me gustaría probar este código con diferentes valores de la variable de entorno. ¿Cómo puedo hacer esto en JUnit?

He visto algunas formas de establecer variables de entorno en Java en general, pero estoy más interesado en el aspecto de las pruebas unitarias, especialmente teniendo en cuenta que las pruebas no deberían interferir entre sí.

vitaut
fuente
Como esto es para pruebas, la regla de prueba de unidad de Reglas del sistema puede ser la mejor respuesta en este momento.
Atifm
3
Solo para aquellos interesados ​​en la misma pregunta mientras usan JUnit 5: stackoverflow.com/questions/46846503/…
Felipe Martins Melo

Respuestas:

199

Las reglas del sistema de la biblioteca proporcionan una regla JUnit para establecer variables de entorno.

import org.junit.contrib.java.lang.system.EnvironmentVariables;

public class EnvironmentVariablesTest {
  @Rule
  public final EnvironmentVariables environmentVariables
    = new EnvironmentVariables();

  @Test
  public void setEnvironmentVariable() {
    environmentVariables.set("name", "value");
    assertEquals("value", System.getenv("name"));
  }
}

Descargo de responsabilidad: soy el autor de las Reglas del sistema.

Stefan Birkner
fuente
1
Estoy usando esto como @ClassRule, ¿necesito restablecerlo o borrarlo después de usarlo? En caso afirmativo, ¿cómo?
Mritunjay
No necesitas hacerlo. La regla restablece automáticamente las variables de entorno originales después de ejecutar todas las pruebas en la clase.
Stefan Birkner
Este enfoque solo funciona para JUnit 4 o una versión superior. No recomendado para JUnit 3 o versión inferior o si combina JUnit 4 y JUnit 3.
RLD
2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Deberá agregar la dependencia com.github.stefanbirkner:system-rulesen su proyecto. Está disponible en MavenCentral.
Jean Bob
2
Aquí están las instrucciones para agregar la dependencia: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier
77

La solución habitual es crear una clase que gestione el acceso a esta variable ambiental, que luego puede burlarse en su clase de prueba.

public class Environment {
    public String getVariable() {
        return System.getenv(); // or whatever
    }
}

public class ServiceTest {
    private static class MockEnvironment {
        public String getVariable() {
           return "foobar";
        }
    }

    @Test public void testService() {
        service.doSomething(new MockEnvironment());
    }
}

La clase bajo prueba obtiene la variable de entorno utilizando la clase de entorno, no directamente de System.getenv ().

Matthew Farwell
fuente
1
Sé que esta pregunta es antigua, pero quería decir que esta es la respuesta correcta. La respuesta aceptada alienta un diseño deficiente con una dependencia oculta del Sistema, mientras que esta respuesta alienta un diseño adecuado que trata al Sistema como una dependencia más que debería inyectarse.
Andrew
30

En una situación similar como esta en la que tuve que escribir Test Case que depende de la variable de entorno , intenté lo siguiente:

  1. Fui por las Reglas del Sistema según lo sugerido por Stefan Birkner . Su uso fue simple. Pero más temprano que tarde, el comportamiento me pareció errático. En una ejecución, funciona, en la siguiente ejecución falla. Investigué y descubrí que las Reglas del sistema funcionan bien con JUnit 4 o una versión superior. Pero en mis casos, estaba usando algunos frascos que dependían de JUnit 3 . Así que me salté las Reglas del sistema . Puede encontrar más información aquí. La anotación @Rule no funciona mientras usa TestSuite en JUnit .
  2. Luego intenté crear la variable de entorno a través de la clase Process Builder proporcionada por Java . Aquí a través de Java Code podemos crear una variable de entorno, pero necesita saber el nombre del proceso o programa que yo no conocía . También crea una variable de entorno para el proceso secundario, no para el proceso principal.

Perdí un día usando los dos enfoques anteriores, pero fue en vano. Entonces Maven vino a mi rescate. Podemos establecer variables de entorno o propiedades del sistema a través de archivo POM de Maven , que creo que es la mejor manera de hacer pruebas unitarias para proyectos basados ​​en Maven . A continuación se muestra la entrada que hice en el archivo POM .

    <build>
      <plugins>
       <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <systemPropertyVariables>
              <PropertyName1>PropertyValue1</PropertyName1>                                                          
              <PropertyName2>PropertyValue2</PropertyName2>
          </systemPropertyVariables>
          <environmentVariables>
            <EnvironmentVariable1>EnvironmentVariableValue1</EnvironmentVariable1>
            <EnvironmentVariable2>EnvironmentVariableValue2</EnvironmentVariable2>
          </environmentVariables>
        </configuration>
      </plugin>
    </plugins>
  </build>

Después de este cambio, ejecuté Test Cases nuevamente y de repente todo funcionó como se esperaba. Para información del lector, exploré este enfoque en Maven 3.x , así que no tengo idea sobre Maven 2.x .

RLD
fuente
2
Esta solución es la mejor y debería ser la aceptada, ya que no necesitará nada adicional como una lib. Maven solo es lo suficientemente útil. Gracias @RLD
Semo
@Semo, sin embargo, requiere Maven, que es un requisito mucho más grande que usar una lib. Acopla la Prueba Junit al pom, y la prueba ahora siempre debe ejecutarse desde mvn, en lugar de ejecutarla directamente en el IDE de la manera habitual.
Chirlo
@Chirlo, depende de con qué quieres que se vincule tu programa. Con Maven, puede configurar en un solo lugar y escribir código limpio y conciso. Si usa la biblioteca, debe escribir código en varios lugares. Con respecto a su punto de ejecución de JUnits, puede ejecutar JUnits desde IDE como Eclipse incluso si usa Maven.
RLD
@RLD, la única forma que conozco en Eclipse sería ejecutarlo como una configuración de ejecución 'Maven' en lugar de un Junit, que es mucho más engorroso y solo tiene salida de texto en lugar de la vista normal de Junit. Y no sigo su punto de código ordenado y conciso y tener que escribir código en varios lugares. Para mí, tener datos de prueba en el pom que luego se usan en una prueba Junit es más oscuro que tenerlos juntos. Estuve en esta situación recientemente y terminé siguiendo el enfoque de MathewFarwell, sin necesidad de bibliotecas / trucos de pom y todo está junto en la misma prueba.
Chirlo
1
Esto hace que las variables de entorno estén codificadas, y no se pueden cambiar de una invocación de System.getenv a la siguiente. ¿Correcto?
Ian Stewart,
12

Creo que la forma más limpia de hacerlo es con Mockito.spy (). Es un poco más liviano que crear una clase separada para burlarse y pasar.

Mueva su recuperación de variables de entorno a otro método:

@VisibleForTesting
String getEnvironmentVariable(String envVar) {
    return System.getenv(envVar);
}

Ahora en su prueba de unidad haga esto:

@Test
public void test() {
    ClassToTest classToTest = new ClassToTest();
    ClassToTest classToTestSpy = Mockito.spy(classToTest);
    Mockito.when(classToTestSpy.getEnvironmentVariable("key")).thenReturn("value");
    // Now test the method that uses getEnvironmentVariable
    assertEquals("changedvalue", classToTestSpy.methodToTest());
}
pgkelley
fuente
12

No creo que esto se haya mencionado todavía, pero también podrías usar Powermockito :

Dado:

package com.foo.service.impl;

public class FooServiceImpl {

    public void doSomeFooStuff() {
        System.getenv("FOO_VAR_1");
        System.getenv("FOO_VAR_2");
        System.getenv("FOO_VAR_3");

        // Do the other Foo stuff
    }
}

Podrías hacer lo siguiente:

package com.foo.service.impl;

import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;

import org.junit.Beforea;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(FooServiceImpl.class)
public class FooServiceImpTest {

    @InjectMocks
    private FooServiceImpl service;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mockStatic(System.class);  // Powermock can mock static and private methods

        when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1");
        when(System.getenv("FOO_VAR_2")).thenReturn("test-foo-var-2");
        when(System.getenv("FOO_VAR_3")).thenReturn("test-foo-var-3");
    }

    @Test
    public void testSomeFooStuff() {        
        // Test
        service.doSomeFooStuff();

        verifyStatic();
        System.getenv("FOO_VAR_1");
        verifyStatic();
        System.getenv("FOO_VAR_2");
        verifyStatic();
        System.getenv("FOO_VAR_3");
    }
}
Mangusta
fuente
8
when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1")causa org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.error
Andremoniy
10

Desacoplar el código Java de la variable de entorno proporcionando un lector de variables más abstracto del que se da cuenta con un entorno VariableReader de su código para probar las lecturas.

Luego, en su prueba, puede dar una implementación diferente del lector de variables que proporciona sus valores de prueba.

La inyección de dependencia puede ayudar en esto.

Andrea Colleoni
fuente
4

Espero que el problema esté resuelto. Solo pensé en decirle mi solución.

Map<String, String> env = System.getenv();
    new MockUp<System>() {
        @Mock           
        public String getenv(String name) 
        {
            if (name.equalsIgnoreCase( "OUR_OWN_VARIABLE" )) {
                return "true";
            }
            return env.get(name);
        }
    };
Muthu
fuente
1
Olvidaste mencionar que estás usando JMockit. :) Independientemente, esta solución también funciona muy bien con JUnit 5
Ryan J. McDonough
2

Aunque creo que esta respuesta es la mejor para los proyectos de Maven, también se puede lograr a través de reflexionar (probado en Java 8 ):

public class TestClass {
    private static final Map<String, String> DEFAULTS = new HashMap<>(System.getenv());
    private static Map<String, String> envMap;

    @Test
    public void aTest() {
        assertEquals("6", System.getenv("NUMBER_OF_PROCESSORS"));
        System.getenv().put("NUMBER_OF_PROCESSORS", "155");
        assertEquals("155", System.getenv("NUMBER_OF_PROCESSORS"));
    }

    @Test
    public void anotherTest() {
        assertEquals("6", System.getenv("NUMBER_OF_PROCESSORS"));
        System.getenv().put("NUMBER_OF_PROCESSORS", "77");
        assertEquals("77", System.getenv("NUMBER_OF_PROCESSORS"));
    }

    /*
     * Restore default variables for each test
     */
    @BeforeEach
    public void initEnvMap() {
        envMap.clear();
        envMap.putAll(DEFAULTS);
    }

    @BeforeAll
    public static void accessFields() throws Exception {
        envMap = new HashMap<>();
        Class<?> clazz = Class.forName("java.lang.ProcessEnvironment");
        Field theCaseInsensitiveEnvironmentField = clazz.getDeclaredField("theCaseInsensitiveEnvironment");
        Field theUnmodifiableEnvironmentField = clazz.getDeclaredField("theUnmodifiableEnvironment");
        removeStaticFinalAndSetValue(theCaseInsensitiveEnvironmentField, envMap);
        removeStaticFinalAndSetValue(theUnmodifiableEnvironmentField, envMap);
    }

    private static void removeStaticFinalAndSetValue(Field field, Object value) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, value);
    }
}
George Z.
fuente
¡Gracias por esto! Mi versión de Java no parece tener theCaseInsensitiveEnvironmenty en su lugar tiene un campo theEnvironment, como el siguiente: `` `envMap = new HashMap <> (); Clase <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Campo theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); Campo theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); `` `
Intenex
-2

Bueno, puedes usar el método setup () para declarar los diferentes valores de tu env. variables en constantes. Luego use estas constantes en los métodos de prueba utilizados para probar los diferentes escenarios.

Cygnusx1
fuente
-2

Si desea recuperar información acerca de la variable de entorno de Java, se puede llamar al método: System.getenv();. Como propiedades, este método devuelve un Mapa que contiene los nombres de las variables como claves y los valores de las variables como los valores del mapa. Aquí hay un ejemplo :

    import java.util.Map;

public class EnvMap {
    public static void main (String[] args) {
        Map<String, String> env = System.getenv();
        for (String envName : env.keySet()) {
            System.out.format("%s=%s%n", envName, env.get(envName));
        }
    }
}

El método getEnv()también puede tomar un argumento. Por ejemplo :

String myvalue = System.getEnv("MY_VARIABLE");

Para probar, haría algo como esto:

public class Environment {
    public static String getVariable(String variable) {
       return  System.getenv(variable);
}

@Test
 public class EnvVariableTest {

     @Test testVariable1(){
         String value = Environment.getVariable("MY_VARIABLE1");
         doSometest(value); 
     }

    @Test testVariable2(){
       String value2 = Environment.getVariable("MY_VARIABLE2");
       doSometest(value); 
     }   
 }
Dimitri
fuente
1
El punto principal no es no acceder a las variables env desde la prueba junit
Tanmoy Bhattacharjee
-2

Uso System.getEnv () para obtener el mapa y lo guardo como un campo, para poder burlarme de él:

public class AAA {

    Map<String, String> environmentVars; 

    public String readEnvironmentVar(String varName) {
        if (environmentVars==null) environmentVars = System.getenv();   
        return environmentVars.get(varName);
    }
}



public class AAATest {

         @Test
         public void test() {
              aaa.environmentVars = new HashMap<String,String>();
              aaa.environmentVars.put("NAME", "value");
              assertEquals("value",aaa.readEnvironmentVar("NAME"));
         }
}
ejaenv
fuente