Wie teste ich Code abhängig von Umgebungsvariablen mit JUnit?

138

Ich habe einen Java-Code, der eine Umgebungsvariable verwendet, und das Verhalten des Codes hängt vom Wert dieser Variablen ab. Ich möchte diesen Code mit verschiedenen Werten der Umgebungsvariablen testen. Wie kann ich das in JUnit machen?

Ich habe einige Möglichkeiten gesehen, Umgebungsvariablen in Java im Allgemeinen festzulegen, aber ich bin mehr an Unit-Test-Aspekten interessiert, insbesondere wenn man bedenkt, dass Tests sich nicht gegenseitig stören sollten.

vitaut
quelle
Da dies zum Testen dient, ist die Einheitentestregel für Systemregeln derzeit möglicherweise die beste Antwort.
Atifm
3
Nur für diejenigen, die an der gleichen Frage interessiert sind, während sie JUnit 5 verwenden: stackoverflow.com/questions/46846503/…
Felipe Martins Melo

Antworten:

198

Die Bibliothek System Rules bietet eine JUnit-Regel zum Festlegen von Umgebungsvariablen.

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

Haftungsausschluss: Ich bin der Autor der Systemregeln.

Stefan Birkner
quelle
1
Ich verwende dies als @ClassRule. Muss ich es nach der Verwendung zurücksetzen oder löschen? Wenn ja, wie?
Mritunjay
Das musst du nicht. Die ursprünglichen Umgebungsvariablen werden von der Regel automatisch zurückgesetzt, nachdem alle Tests in der Klasse ausgeführt wurden.
Stefan Birkner
Dieser Ansatz funktioniert nur für JUnit 4 oder höher. Nicht empfohlen für JUnit 3 oder eine niedrigere Version oder wenn Sie JUnit 4 und JUnit 3 mischen.
RLD
2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Sie müssen die Abhängigkeit com.github.stefanbirkner:system-rulesin Ihrem Projekt hinzufügen . Es ist in MavenCentral verfügbar.
Jean Bob
2
Hier sind die Anweisungen zum Hinzufügen der Abhängigkeit: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier
76

Die übliche Lösung besteht darin, eine Klasse zu erstellen, die den Zugriff auf diese Umgebungsvariable verwaltet, die Sie dann in Ihrer Testklasse verspotten können.

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

Die zu testende Klasse erhält dann die Umgebungsvariable mithilfe der Environment-Klasse, nicht direkt von System.getenv ().

Matthew Farwell
quelle
1
Ich weiß, dass diese Frage alt ist, aber ich wollte sagen, dass dies die richtige Antwort ist. Die akzeptierte Antwort fördert ein schlechtes Design mit einer versteckten Abhängigkeit vom System, während diese Antwort ein korrektes Design fördert, das das System als eine weitere Abhängigkeit behandelt, die injiziert werden sollte.
Andrew
30

In einer ähnlichen Situation wie diese , wo ich zu schreiben hatte Testfall , die abhängig ist von Umgebungsvariablen , habe ich versucht , folgende:

  1. Ich habe mich für Systemregeln entschieden, wie von Stefan Birkner vorgeschlagen . Die Verwendung war einfach. Aber früher als später fand ich das Verhalten unberechenbar. In einem Lauf funktioniert es, im nächsten Lauf schlägt es fehl. Ich habe nachgeforscht und festgestellt, dass Systemregeln mit JUnit 4 oder einer höheren Version gut funktionieren. In meinen Fällen verwendete ich jedoch einige Gläser, die von JUnit 3 abhängig waren . Also habe ich die Systemregeln übersprungen . Mehr dazu finden Sie hier @Rule Annotation funktioniert nicht, wenn Sie TestSuite in JUnit verwenden .
  2. Als Nächstes habe ich versucht, eine Umgebungsvariable mithilfe der von Java bereitgestellten Process Builder- Klasse zu erstellen . Hier können wir über Java Code eine Umgebungsvariable erstellen, aber Sie müssen den Prozess- oder Programmnamen kennen, den ich nicht kannte. Außerdem wird eine Umgebungsvariable für den untergeordneten Prozess erstellt, nicht für den Hauptprozess.

Ich habe einen Tag mit den beiden oben genannten Ansätzen verschwendet, aber ohne Erfolg. Dann kam Maven zu meiner Rettung. Wir können Umgebungsvariablen oder Systemeigenschaften über die Maven-POM- Datei festlegen. Dies ist meiner Meinung nach der beste Weg, um Unit-Tests für Maven- basierte Projekte durchzuführen . Unten ist der Eintrag, den ich in der POM- Datei gemacht habe.

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

Nach dieser Änderung führte ich erneut Testfälle aus und plötzlich funktionierten alle wie erwartet. Für Leser Informationen, erkundete ich diesen Ansatz in Maven 3.x , so habe ich keine Ahnung über Maven 2.x .

RLD
quelle
2
Diese Lösung ist die beste und sollte akzeptiert werden, da Sie nichts Zusätzliches wie eine Bibliothek benötigen. Maven allein ist praktisch genug. Vielen Dank, dass Sie @RLD
Semo
@ Semo erfordert es allerdings Maven, was eine viel größere Anforderung ist als die Verwendung einer Bibliothek. Es koppelt den Junit-Test mit dem POM, und der Test muss jetzt immer von mvn ausgeführt werden, anstatt ihn wie gewohnt direkt auf der IDE auszuführen.
Chirlo
@Chirlo, es hängt davon ab, mit was Ihr Programm verknüpft werden soll. Mit Maven können Sie an einem Ort konfigurieren und sauberen und präzisen Code schreiben. Wenn Sie eine Bibliothek verwenden, müssen Sie Code an mehreren Stellen schreiben. In Bezug auf die Ausführung von JUnits können Sie JUnits von IDE wie Eclipse aus ausführen, selbst wenn Sie Maven verwenden.
RLD
@RLD, die einzige Möglichkeit, die ich in Eclipse kenne, besteht darin, es als 'Maven'-Ausführungskonfiguration anstelle eines Junit auszuführen, das viel umständlicher ist und nur eine Textausgabe anstelle der normalen Junit-Ansicht hat. Und ich folge Ihrem Standpunkt, ordentlichen und prägnanten Code zu verwenden und Code an mehreren Stellen schreiben zu müssen, nicht ganz. Für mich ist es dunkler, Testdaten im POM zu haben, die dann in einem Junit-Test verwendet werden, als sie zusammen zu haben. Ich war vor kurzem in dieser Situation und folgte dem Ansatz von MathewFarwell, brauchte keine Bibliotheken / Pom-Tricks und alles ist zusammen im selben Test.
Chirlo
1
Dadurch werden die Umgebungsvariablen fest codiert und können nicht von einem Aufruf von System.getenv zum nächsten geändert werden. Richtig?
Ian Stewart
12

Ich denke, der sauberste Weg, dies zu tun, ist mit Mockito.spy (). Es ist etwas leichter als das Erstellen einer separaten Klasse zum Verspotten und Weitergeben.

Verschieben Sie das Abrufen Ihrer Umgebungsvariablen auf eine andere Methode:

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

Führen Sie nun in Ihrem Komponententest Folgendes aus:

@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
quelle
12

Ich glaube, das wurde noch nicht erwähnt, aber Sie könnten auch Powermockito verwenden :

Gegeben:

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

Sie können Folgendes tun:

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
quelle
8
when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1")verursacht org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.Fehler
Andremoniy
10

Entkoppeln Sie den Java-Code von der Umgebungsvariablen und stellen Sie einen abstrakteren Variablenleser bereit, den Sie mit einem EnvironmentVariableReader für Ihren Code zum Testen von Lesevorgängen realisieren.

Dann können Sie in Ihrem Test eine andere Implementierung des Variablenlesers angeben, der Ihre Testwerte bereitstellt.

Die Abhängigkeitsinjektion kann dabei helfen.

Andrea Colleoni
quelle
4

Hoffe, das Problem ist behoben. Ich dachte nur daran, meine Lösung zu erzählen.

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
quelle
1
Sie haben vergessen zu erwähnen, dass Sie JMockit verwenden. :) Unabhängig davon funktioniert diese Lösung auch hervorragend mit JUnit 5
Ryan J. McDonough
1

Obwohl ich denke, dass diese Antwort die beste für Maven-Projekte ist, kann sie auch über Reflect erreicht werden (getestet 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);
    }
}
George Z.
quelle
Danke dafür! Meine Java-Version scheint kein theCaseInsensitiveEnvironmentFeld zu haben und hat stattdessen theEnvironmentFolgendes: `` `envMap = new HashMap <> (); Klasse <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Feld theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); Feld theUnmodizableEnvironmentField = clazz.getDeclaredField ("theUnmodizableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodizableEnvironmentField, envMap); `` `
Intenex
-2

Nun, Sie können die setup () -Methode verwenden, um die verschiedenen Werte Ihrer Umgebung zu deklarieren. Variablen in Konstanten. Verwenden Sie diese Konstanten dann in den Testmethoden, die zum Testen der verschiedenen Szenarien verwendet werden.

Cygnusx1
quelle
-2

Wenn Sie Informationen über die Umgebungsvariable in Java abrufen möchten, können Sie die folgende Methode aufrufen : System.getenv();. Als Eigenschaften gibt diese Methode eine Map zurück, die die Variablennamen als Schlüssel und die Variablenwerte als Map-Werte enthält. Hier ist ein Beispiel :

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

Die Methode getEnv()kann auch ein Argument annehmen. Zum Beispiel :

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

Zum Testen würde ich so etwas machen:

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
quelle
1
Der Hauptpunkt ist nicht, nicht auf die env-Variablen aus dem Junit-Test
zuzugreifen
-2

Ich benutze System.getEnv (), um die Karte zu erhalten, und behalte sie als Feld, damit ich sie verspotten kann:

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
quelle