JUnitを使用して環境変数に依存するコードをテストする方法は?


140

環境変数を使用するJavaコードがあり、コードの動作はこの変数の値に依存します。このコードを環境変数のさまざまな値でテストしたいと思います。JUnitでこれを行うにはどうすればよいですか?

Java環境変数を設定する一般的な方法をいくつかきましたが、特にテストが互いに干渉してはならないことを考えると、Javaのユニットテストの側面に関心があります。


これはテスト用であるため、現在のところ、システムルールの単体テストルールが最良の答えになる場合があります。
Atifm 2017年

3
ただ、JUnitの5を使用しながら、同じ質問に興味のある人のために:stackoverflow.com/questions/46846503/...
フェリペ・メロ・マルティンス

回答:


199

ライブラリのシステムルールは、環境変数を設定するためのJUnitルールを提供します。

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

免責事項:私はシステムルールの作成者です。


1
これを@ClassRuleとして使用していますが、使用後にリセットまたはクリアする必要がありますか。
Mritunjay

必要はありません。元の環境変数は、クラス内のすべてのテストが実行された後、ルールによって自動的にリセットされます。
Stefan Birkner 16

このアプローチは、JUnit 4以降のバージョンでのみ機能します。JUnitの3以下のバージョンのか、JUnitの4とJUnit 3混ぜる場合はお勧めしません
RLD

2
import org.junit.contrib.java.lang.system.EnvironmentVariables;com.github.stefanbirkner:system-rulesプロジェクトに依存関係を追加する必要があります。MavenCentralで利用できます。
ジャン・ボブ・

2
依存関係を追加する手順は次のとおりです。stefanbirkner.github.io
Guilherme Garnier

77

通常の解決策は、この環境変数へのアクセスを管理するクラスを作成することです。この環境変数をテストクラスにモックできます。

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

テスト中のクラスは、System.getenv()から直接ではなく、Environmentクラスを使用して環境変数を取得します。


1
私はこの質問が古いことを知っていますが、これが正解だと言いたかったのです。受け入れられた回答は、システムへの依存関係が隠された貧弱な設計を奨励しますが、この回答は、システムを注入すべき別の依存関係として扱う適切な設計を奨励します。
アンドリュー

30

同様の状況で、環境変数に依存するテストケースを記述しなければならなかったので、以下を試しました。

  1. Stefan Birknerの提案に従って、システムルール採用しました。使い方は簡単でした。しかし、遅かれ早かれ、私はふるまいを不安定にしました。1回の実行では機能し、次の実行では失敗します。調べたところ、システムルールはJUnit 4以降のバージョンでうまく機能することがわかりました。しかし、私の場合は、JUnit 3に依存するいくつかのjarを使用していました。だから私はシステムルールをスキップしました。詳細については、JUnitでTestSuiteを使用しているときに@Ruleアノテーションが機能しないことをご覧ください
  2. 次に、Javaが提供するProcess Builderクラスを介して環境変数を作成しようとしました。ここでは、Javaコードを使用して環境変数を作成できますが、私が知らなかったプロセスまたはプログラムの名前を知る必要があります。また、メインプロセスではなく、子プロセスの環境変数を作成します。

上記の2つの方法を使用して1日を無駄にしましたが、役に立ちませんでした。その後、Mavenが私の救助にやってきた。Mavenベースのプロジェクトの単体テストを行うための最良の方法であると考えるMaven POMファイルを使用して、環境変数またはシステムプロパティを設定できます。以下は、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>

この変更後、テストケースを再度実行したところ、突然すべて期待どおりに機能しました。読者の情報については、このアプローチをMaven 3.xで探索したので、Maven 2.xについてはわかりません。


2
libのような追加のものが必要ないため、このソリューションが最適であり、受け入れられるはずです。Mavenだけでも十分便利です。ありがとう@RLD
Semo

@Semoにはmavenが必要ですが、これはlibを使用するよりもはるかに大きな要件です。Junitテストをpomに結合します。通常の方法でIDEで直接実行するのではなく、テストを常にmvnから実行する必要があります。
Chirlo、

@Chirlo、それはあなたがあなたのプログラムを結びつけたいものに依存します。Mavenを使用すると、1か所で構成して、簡潔で簡潔なコードを記述できます。ライブラリを使用する場合、複数の場所でコードを記述する必要があります。JUnitsを実行する目的については、Mavenを使用している場合でも、EclipseのようなIDEからJUnitsを実行できます。
RLD

@RLD、私がEclipseで知っている唯一の方法は、JUnitの代わりに「Maven」実行構成として実行することです。JUnitは、はるかに扱いにくく、通常のJUnitビューの代わりにテキスト出力しかありません。そして、きちんとした簡潔なコードを使用して、複数の場所でコードを記述しなければならないというあなたの主張には完全には従いません。私にとっては、JUnitテストで使用されるテストデータをpomに持つことは、それらを一緒にすることよりもあいまいです。私は最近この状況にあり、MathewFarwellのアプローチに従ってしまいました。ライブラリ/ pomトリックは必要なく、すべてが同じテストにまとめられています。
Chirlo

1
これにより、環境変数がハードコードされ、System.getenvの呼び出しから次の呼び出しに変更することはできません。正しい?
Ian Stewart

12

これを行う最もクリーンな方法はMockito.spy()を使用することだと思います。これは、モックして渡すための個別のクラスを作成するよりも少し軽量です。

環境変数のフェッチを別のメソッドに移動します。

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

ユニットテストでこれを行います:

@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

これについてはまだ言及されていないと思いますが、Powermockitoを使用することもできます

与えられた:

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

次のことができます。

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")org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.エラーの原因
Andremoniy 2017年

10

Javaコードを環境変数から切り離して、読み取りをテストするコードをEnvironmentVariableReaderで実現する、より抽象的な変数リーダーを提供します。

次に、テストで、テスト値を提供する変数リーダーの異なる実装を提供できます。

依存性注入はこれに役立ちます。



4

問題が解決することを願っています。私は自分の解決策を教えようと思いました。

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
JMockitを使用していることを忘れていました。:)とにかく、このソリューションはJUnit 5でもうまく機能します
Ryan J. McDonough

2

この回答はMavenプロジェクトに最適だと思いますが、reflectを使用しても実現できます(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);
    }
}

これをありがとう!私のバージョンのJavaには、次のようなtheCaseInsensitiveEnvironmentフィールドがないようですが、代わりにフィールドtheEnvironmentがあります。 `` `envMap = new HashMap <>(); クラス<?> clazz = Class.forName( "java.lang.ProcessEnvironment"); フィールドtheEnvironmentField = clazz.getDeclaredField( "theEnvironment"); フィールドtheUnmodifiableEnvironmentField = clazz.getDeclaredField( "theUnmodifiableEnvironment"); removeStaticFinalAndSetValue(theEnvironmentField、envMap); removeStaticFinalAndSetValue(theUnmodifiableEnvironmentField、envMap); `` `
インテネックス

-2

さて、setup()メソッドを使用して、envのさまざまな値を宣言できます。定数内の変数。次に、さまざまなシナリオをテストするために使用されるテストメソッドでこれらの定数を使用します。


-2

Javaで環境変数に関する情報を取得する場合は、メソッドを呼び出すことができますSystem.getenv();。このメソッドはプロパティとして、変数名をキーとして含み、変数値をマップ値として含むマップを返します。次に例を示します。

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

このメソッドgetEnv()は引数を取ることもできます。例えば ​​:

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

テストのために、私はこのようなことをします:

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
重要な点は、junitテストからenv変数にアクセスしないことではありません
Tanmoy Bhattacharjee

-2

System.getEnv()を使用してマップを取得し、フィールドとして保持するので、モックを作成できます。

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"));
         }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.