環境変数を使用するJavaコードがあり、コードの動作はこの変数の値に依存します。このコードを環境変数のさまざまな値でテストしたいと思います。JUnitでこれを行うにはどうすればよいですか?
Javaで環境変数を設定する一般的な方法をいくつか見てきましたが、特にテストが互いに干渉してはならないことを考えると、Javaのユニットテストの側面に関心があります。
環境変数を使用するJavaコードがあり、コードの動作はこの変数の値に依存します。このコードを環境変数のさまざまな値でテストしたいと思います。JUnitでこれを行うにはどうすればよいですか?
Javaで環境変数を設定する一般的な方法をいくつか見てきましたが、特にテストが互いに干渉してはならないことを考えると、Javaのユニットテストの側面に関心があります。
回答:
ライブラリのシステムルールは、環境変数を設定するための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"));
}
}
免責事項:私はシステムルールの作成者です。
import org.junit.contrib.java.lang.system.EnvironmentVariables;
com.github.stefanbirkner:system-rules
プロジェクトに依存関係を追加する必要があります。MavenCentralで利用できます。
通常の解決策は、この環境変数へのアクセスを管理するクラスを作成することです。この環境変数をテストクラスにモックできます。
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クラスを使用して環境変数を取得します。
同様の状況で、環境変数に依存するテストケースを記述しなければならなかったので、以下を試しました。
上記の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についてはわかりません。
これを行う最もクリーンな方法は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());
}
これについてはまだ言及されていないと思いますが、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");
}
}
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'.
エラーの原因
Javaコードを環境変数から切り離して、読み取りをテストするコードをEnvironmentVariableReaderで実現する、より抽象的な変数リーダーを提供します。
次に、テストで、テスト値を提供する変数リーダーの異なる実装を提供できます。
依存性注入はこれに役立ちます。
この回答の質問に、私は、Javaから環境変数を設定するにはどうすればよいですか?System.getenv()の(変更不可能な)マップを変更する方法を提供します。したがって、OS環境変数の値は実際には変更されませんが、System.getenvが返すものを変更するため、単体テストに使用できます。
問題が解決することを願っています。私は自分の解決策を教えようと思いました。
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);
}
};
この回答は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);
}
}
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); `` `
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);
}
}
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"));
}
}