Comment tester le code dépendant des variables d'environnement à l'aide de JUnit?


140

J'ai un morceau de code Java qui utilise une variable d'environnement et le comportement du code dépend de la valeur de cette variable. Je voudrais tester ce code avec différentes valeurs de la variable d'environnement. Comment puis-je faire cela dans JUnit?

J'ai vu des moyens de définir des variables d'environnement en Java en général, mais je suis plus intéressé par l'aspect des tests unitaires, d'autant plus que les tests ne devraient pas interférer les uns avec les autres.


Puisqu'il s'agit de tests, la règle de test unitaire des règles système peut être la meilleure réponse à l'heure actuelle.
Atifm

3
Juste pour ceux qui sont intéressés par la même question en utilisant JUnit 5: stackoverflow.com/questions/46846503/...
Felipe Martins Melo

Réponses:


199

La bibliothèque System Rules fournit une règle JUnit pour définir les variables d'environnement.

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"));
  }
}

Avertissement: je suis l'auteur des règles système.


1
J'utilise ceci comme @ClassRule, dois-je le réinitialiser ou l'effacer après utilisation, si oui, comment?
Mritunjay

Vous n'en avez pas besoin. Les variables d'environnement d'origine sont automatiquement réinitialisées par la règle après l'exécution de tous les tests de la classe.
Stefan Birkner

Cette approche ne fonctionne que pour JUnit 4 ou version supérieure. Non recommandé pour JUnit 3 ou version inférieure ou si vous mélangez JUnit 4 et JUnit 3.
RLD

2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Vous devrez ajouter la dépendance com.github.stefanbirkner:system-rulesdans votre projet. Il est disponible dans MavenCentral.
Jean Bob

2
Voici les instructions pour ajouter la dépendance: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier

77

La solution habituelle est de créer une classe qui gère l'accès à cette variable d'environnement, que vous pouvez ensuite simuler dans votre classe de test.

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 classe testée obtient alors la variable d'environnement en utilisant la classe Environment, et non directement à partir de System.getenv ().


1
Je sais que cette question est ancienne, mais je voulais dire que c'est la bonne réponse. La réponse acceptée encourage une mauvaise conception avec une dépendance cachée sur System, alors que cette réponse encourage une conception appropriée traitant System comme une simple dépendance à injecter.
Andrew

30

Dans une situation similaire comme celle-ci où je devais écrire un cas de test qui dépend de la variable d'environnement , j'ai essayé de suivre:

  1. J'ai opté pour des règles système comme suggéré par Stefan Birkner . Son utilisation était simple. Mais tôt que tard, j'ai trouvé le comportement erratique. Dans une exécution, cela fonctionne, dans la toute prochaine exécution, cela échoue. J'ai enquêté et j'ai trouvé que les règles système fonctionnent bien avec JUnit 4 ou une version supérieure. Mais dans mon cas, j'utilisais des Jars qui dépendaient de JUnit 3 . J'ai donc ignoré les règles système . Vous pouvez en savoir plus ici L'annotation @Rule ne fonctionne pas lors de l'utilisation de TestSuite dans JUnit .
  2. Ensuite, j'ai essayé de créer une variable d'environnement via la classe Process Builder fournie par Java . Ici, via Java Code, nous pouvons créer une variable d'environnement, mais vous devez connaître le nom du processus ou du programme, ce que je n'ai pas fait. En outre, il crée une variable d'environnement pour le processus enfant, pas pour le processus principal.

J'ai perdu une journée en utilisant les deux approches ci-dessus, mais en vain. Puis Maven est venu à mon secours. Nous pouvons définir des variables d'environnement ou des propriétés système via le fichier Maven POM , ce qui, à mon avis, est le meilleur moyen de faire des tests unitaires pour un projet basé sur Maven . Voici l'entrée que j'ai faite dans le fichier 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>

Après ce changement, j'ai de nouveau exécuté des cas de test et tout a fonctionné comme prévu. Pour l'information du lecteur, j'exploré cette approche dans 3.x Maven , je ne ai aucune idée sur Maven 2.x .


2
Cette solution est la meilleure et devrait être celle acceptée, car vous n'aurez besoin de rien de plus comme une bibliothèque. Maven seul est assez pratique. Merci @RLD
Semo

@Semo nécessite cependant maven, ce qui est bien plus important que l'utilisation d'une bibliothèque. Il couple le test Junit au pom, et le test doit maintenant toujours être exécuté à partir de mvn, au lieu de l'exécuter directement sur l'IDE de la manière habituelle.
Chirlo

@Chirlo, cela dépend de ce à quoi vous voulez que votre programme soit lié. En utilisant Maven, vous pouvez configurer en un seul endroit et écrire un code net et concis. Si vous utilisez une bibliothèque, vous devez écrire du code à plusieurs endroits. En ce qui concerne votre point d'exécution de JUnits, vous pouvez exécuter des JUnits à partir de l'IDE comme Eclipse même si vous utilisez Maven.
RLD

@RLD, le seul moyen que je connaisse dans Eclipse serait de l'exécuter en tant que configuration d'exécution 'Maven' au lieu d'un Junit qui est beaucoup plus encombrant et n'a qu'une sortie de texte au lieu de la vue Junit normale. Et je ne suis pas tout à fait votre point de vue du code net et concis et du devoir d'écrire du code à plusieurs endroits. Pour moi, avoir des données de test dans le pom qui est ensuite utilisé dans un test Junit est plus obscur que de les avoir ensemble. J'étais dans cette situation récemment et j'ai fini par suivre l'approche de MathewFarwell, pas besoin de bibliothèques / astuces pom et tout est réuni dans le même test.
Chirlo

1
Cela rend les variables d'environnement codées en dur et elles ne peuvent pas être modifiées d'un appel de System.getenv à l'autre. Correct?
Ian Stewart

12

Je pense que le moyen le plus propre de le faire est d'utiliser Mockito.spy (). C'est un peu plus léger que de créer une classe séparée pour se moquer et faire circuler.

Déplacez votre récupération de variable d'environnement vers une autre méthode:

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

Maintenant, dans votre test unitaire, procédez comme suit:

@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());
}

12

Je ne pense pas que cela ait encore été mentionné, mais vous pouvez également utiliser Powermockito :

Donné:

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
    }
}

Vous pouvez faire ce qui suit:

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");
    }
}

8
when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1")provoque une org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.erreur
Andremoniy

10

Découplez le code Java de la variable Environment en fournissant un lecteur de variable plus abstrait que vous réalisez avec un EnvironmentVariableReader à partir duquel votre code testera les lectures.

Ensuite, dans votre test, vous pouvez donner une implémentation différente du lecteur de variables qui fournit vos valeurs de test.

L'injection de dépendance peut y contribuer.



4

J'espère que le problème est résolu. J'ai juste pensé à dire ma solution.

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);
        }
    };

1
Vous avez oublié de mentionner que vous utilisez JMockit. :) Quoi qu'il en soit, cette solution fonctionne également très bien avec JUnit 5
Ryan J. McDonough

2

Même si je pense que cette réponse est la meilleure pour les projets Maven, elle peut également être obtenue via Reflect (testé 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);
    }
}

Merci pour cela! Ma version de Java ne semble pas avoir theCaseInsensitiveEnvironmentet a à la place un champ theEnvironment, comme le suivant: `` `` envMap = new HashMap <> (); Classe <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Champ theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); Champ theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); ``
Intenex le

-2

Eh bien, vous pouvez utiliser la méthode setup () pour déclarer les différentes valeurs de votre env. variables en constantes. Utilisez ensuite ces constantes dans les méthodes de test utilisées pour tester les différents scénarios.


-2

Si vous souhaitez récupérer des informations sur la variable d'environnement en Java, vous pouvez appeler la méthode: System.getenv();. En tant que propriétés, cette méthode retourne une carte contenant les noms de variable sous forme de clés et les valeurs de variable comme valeurs de carte. Voici un exemple :

    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));
        }
    }
}

La méthode getEnv()peut également prendre un argument. Par exemple :

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

Pour les tests, je ferais quelque chose comme ceci:

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); 
     }   
 }

1
Le point principal n'est pas de ne pas accéder aux variables env du test
junit

-2

J'utilise System.getEnv () pour obtenir la carte et je la garde en tant que champ, donc je peux me moquer:

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"));
         }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.