Come testare il codice dipendente dalle variabili d'ambiente usando JUnit?


140

Ho un pezzo di codice Java che utilizza una variabile di ambiente e il comportamento del codice dipende dal valore di questa variabile. Vorrei testare questo codice con diversi valori della variabile d'ambiente. Come posso farlo in JUnit?

Ho visto alcuni modi per impostare le variabili di ambiente in Java in generale, ma sono più interessato all'aspetto unit test di esso, soprattutto considerando che i test non dovrebbero interferire tra loro.


Poiché questo è per il test, la regola del test unità delle Regole di sistema potrebbe essere la migliore risposta al momento.
Atifm,

3
Solo per coloro che sono interessati alla stessa domanda durante l'utilizzo di JUnit 5: stackoverflow.com/questions/46846503/…
Felipe Martins Melo,

Risposte:


199

La libreria Regole di sistema fornisce una regola JUnit per l'impostazione delle variabili di ambiente.

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

Disclaimer: sono l'autore delle Regole di sistema.


1
Lo sto usando come @ClassRule, devo ripristinarlo o cancellarlo dopo l'uso, se sì, allora come?
Mritunjay,

Non è necessario. Le variabili di ambiente originali vengono automaticamente ripristinate dalla regola dopo l'esecuzione di tutti i test nella classe.
Stefan Birkner,

Questo approccio funziona solo per JUnit 4 o versione successiva. Non consigliato per JUnit 3 o versione precedente o se si mescolano JUnit 4 e JUnit 3.
RLD

2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Dovrai aggiungere la dipendenza com.github.stefanbirkner:system-rulesnel tuo progetto. È disponibile in MavenCentral.
Jean Bob,

2
Ecco le istruzioni per aggiungere la dipendenza: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier

77

La solita soluzione è quella di creare una classe che gestisca l'accesso a questa variabile ambientale, che è quindi possibile prendere in giro nella classe di 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 sotto test ottiene quindi la variabile d'ambiente usando la classe Environment, non direttamente da System.getenv ().


1
So che questa domanda è vecchia, ma volevo dire che questa è la risposta corretta. La risposta accettata incoraggia la cattiva progettazione con una dipendenza nascosta dal Sistema, mentre questa risposta incoraggia una corretta progettazione che tratta il Sistema come solo un'altra dipendenza che dovrebbe essere iniettata.
Andrew

30

In una situazione simile come questa in cui ho dovuto scrivere Test Case che dipende dalla variabile d'ambiente , ho provato a seguire:

  1. Ho scelto le regole di sistema come suggerito da Stefan Birkner . Il suo uso era semplice. Ma prima che poi, ho trovato il comportamento irregolare. In una corsa, funziona, nella corsa successiva fallisce. Ho studiato e trovato che le regole di sistema funzionano bene con JUnit 4 o versione successiva. Ma nei miei casi, stavo usando alcuni vasetti che dipendevano da JUnit 3 . Quindi ho saltato le regole di sistema . Ulteriori informazioni al riguardo sono disponibili qui L'annotazione @Rule non funziona durante l'utilizzo di TestSuite in JUnit .
  2. Successivamente ho provato a creare la variabile di ambiente tramite la classe Process Builder fornita da Java . Qui tramite Java Code possiamo creare una variabile d'ambiente, ma è necessario conoscere il processo o il nome del programma che non ho fatto. Inoltre crea una variabile d'ambiente per il processo figlio, non per il processo principale.

Ho sprecato una giornata usando i due approcci precedenti, ma senza risultati. Quindi Maven venne in mio soccorso. Possiamo impostare le variabili di ambiente o le proprietà di sistema tramite il file POM Maven , che penso sia il modo migliore per eseguire i test unitari per Maven progetti basati su . Di seguito è la voce che ho fatto nel file 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>

Dopo questa modifica, ho eseguito nuovamente Test Case e all'improvviso tutto ha funzionato come previsto. Per informazioni del lettore, ho esplorato questo approccio in Maven 3.x , quindi non ho idea su Maven 2.x .


2
Questa soluzione è la migliore e dovrebbe essere accettata, perché non avrai bisogno di qualcosa di aggiuntivo come una lib. Maven da solo è abbastanza utile. Grazie @RLD
Semo

@Semo richiede maven, che è un requisito molto più grande rispetto all'utilizzo di una lib. Abbina il Junit Test al pom e ora il test deve sempre essere eseguito da MVN, invece di eseguirlo direttamente sull'IDE nel solito modo.
Chirlo,

@Chirlo, dipende da cosa vuoi che il tuo programma leghi. Usando Maven, puoi configurare in un unico posto e scrivere codice pulito e conciso. Se usi la libreria, devi scrivere il codice in più punti. Per quanto riguarda il punto in cui si esegue JUnits, è possibile eseguire JUnits da IDE come Eclipse anche se si utilizza Maven.
RLD

@RLD, l'unico modo che conosco in Eclipse sarebbe eseguirlo come una configurazione di esecuzione 'Maven' invece di un Junit che è molto più ingombrante e ha solo l'output di testo invece della normale vista Junit. E non seguo abbastanza il tuo punto di codice pulito e conciso e devo scrivere codice in più punti. Per me, avere i dati di test nel pom che viene quindi utilizzato in un test Junit è più oscuro che averli insieme. Sono stato di recente in questa situazione e ho finito per seguire l'approccio di MathewFarwell, senza bisogno di librerie / trucchi di pom e tutto è insieme nello stesso test.
Chirlo

1
Ciò rende le variabili di ambiente codificate in modo rigido e non possono essere modificate da una chiamata a System.getenv alla successiva. Corretta?
Ian Stewart,

12

Penso che il modo più pulito per farlo sia con Mockito.spy (). È un po 'più leggero rispetto alla creazione di una classe separata per deridere e passare.

Sposta il recupero della variabile di ambiente in un altro metodo:

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

Ora nel tuo unit test fai questo:

@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

Non credo che questo sia stato ancora menzionato, ma potresti anche usare Powermockito :

Dato:

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

Puoi fare quanto segue:

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")causa org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.errore
Andremoniy,

10

Disaccoppia il codice Java dalla variabile Environment fornendo un lettore di variabili più astratto che realizzi con EnvironmentVariableReader il tuo codice per testare le letture.

Quindi nel tuo test puoi dare un'implementazione diversa del lettore di variabili che fornisce i tuoi valori di test.

Iniezione di dipendenza può aiutare in questo.



4

Spero che il problema sia stato risolto. Ho solo pensato di dire la mia soluzione.

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
Hai dimenticato di dire che stai usando JMockit. :) Indipendentemente da ciò, questa soluzione funziona perfettamente anche con JUnit 5
Ryan J. McDonough,

2

Anche se penso che questa risposta sia la migliore per i progetti Maven, può essere raggiunta anche tramite reflection (testato in 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);
    }
}

Grazie per questo! La mia versione di Java non sembra avere theCaseInsensitiveEnvironmente invece ha un campo theEnvironment, come il seguente: `` `envMap = new HashMap <> (); Class <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Campo theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); Field theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); ``
Intenex,

-2

Bene, puoi usare il metodo setup () per dichiarare i diversi valori del tuo env. variabili nelle costanti. Quindi utilizzare queste costanti nei metodi di test utilizzati per testare il diverso scenario.


-2

Se si desidera recuperare informazioni riguardo la variabile d'ambiente in Java, è possibile chiamare il metodo: System.getenv();. Come proprietà, questo metodo restituisce una mappa contenente i nomi delle variabili come chiavi e i valori delle variabili come valori della mappa. Ecco un esempio:

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

Il metodo getEnv()può anche prendere un argomento. Per esempio :

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

Per i test, farei una cosa del genere:

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
Il punto principale non è accedere alle variabili env dal test junit
Tanmoy Bhattacharjee

-2

Uso System.getEnv () per ottenere la mappa e tengo come un campo, quindi posso deriderlo:

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"));
         }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.