我有一段使用环境变量的Java代码,代码的行为取决于这个变量的值。我想测试不同值的环境变量下的代码行为。在JUnit中如何实现呢?
我看到一些 Java 中设置环境变量的方法(链接),但我更关心单元测试方面的问题,特别是考虑到测试之间不应该互相干扰。
我有一段使用环境变量的Java代码,代码的行为取决于这个变量的值。我想测试不同值的环境变量下的代码行为。在JUnit中如何实现呢?
我看到一些 Java 中设置环境变量的方法(链接),但我更关心单元测试方面的问题,特别是考虑到测试之间不应该互相干扰。
这个名为System Lambda的库提供了一个用于设置环境变量的方法withEnvironmentVariable
。
import static com.github.stefanbirkner.systemlambda.SystemLambda.*;
public void EnvironmentVariablesTest {
@Test
public void setEnvironmentVariable() {
String value = withEnvironmentVariable("name", "value")
.execute(() -> System.getenv("name"));
assertEquals("value", value);
}
}
对于Java 5到7,库 System Rules 具有名为 EnvironmentVariables
的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"));
}
}
全面披露:我是这两个库的作者。
com.github.stefanbirkner:system-rules
。它可以在MavenCentral上获取。同时,导入 org.junit.contrib.java.lang.system.EnvironmentVariables
包。 - Jean Bob通常的解决方案是创建一个类来管理对这个环境变量的访问,然后在您的测试类中可以模拟它。
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());
}
}
测试类通过使用环境变量类(Environment class)获取环境变量,而不是直接从System.getenv()方法中获取。 <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>
我认为最简洁的方法是使用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());
}
对于JUnit 4用户,建议使用System Lambda,如Stefan Birkner所建议。
如果您正在使用JUnit 5,则有JUnit Pioneer扩展包。它带有@ClearEnvironmentVariable
和@SetEnvironmentVariable
。参考文档:
@ClearEnvironmentVariable
和@SetEnvironmentVariable
注释可用于清除或设置测试执行的环境变量值。这两个注释均适用于测试方法和类级别,可重复使用并组合使用。在执行已注释的方法后,将恢复注释中提到的变量的原始值,如果没有,则将其清除。在测试期间更改的其他环境变量不会被恢复。
例如:
@Test
@ClearEnvironmentVariable(key = "SOME_VARIABLE")
@SetEnvironmentVariable(key = "ANOTHER_VARIABLE", value = "new value")
void test() {
assertNull(System.getenv("SOME_VARIABLE"));
assertEquals("new value", System.getenv("ANOTHER_VARIABLE"));
}
我认为这还没有被提到过,但你也可以使用 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()需要一个参数,必须是“对模拟对象的方法调用”。
错误。 - Andremoniy解耦Java代码与环境变量,提供一个更抽象的变量读取器,通过EnvironmentVariableReader来实现你的代码进行测试读取。
然后在你的测试中,你可以提供一个不同的变量读取器实现,以提供你的测试值。
依赖注入可以帮助解决这个问题。
这个答案回答了一个关于如何在Java中设置环境变量的问题。它提供了一种修改System.getenv()中(不可修改的)Map的方法。因此,尽管它并没有真正改变操作系统环境变量的值,但可以用于单元测试,因为它确实改变了System.getenv方法返回的值。
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<>();
Class> clazz = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = clazz.getDeclaredField("theEnvironment");
Field theUnmodifiableEnvironmentField = clazz.getDeclaredField("theUnmodifiableEnvironment");
removeStaticFinalAndSetValue(theEnvironmentField, envMap);
removeStaticFinalAndSetValue(theUnmodifiableEnvironmentField, envMap);
- Intenex希望问题已经解决。我只是想告诉你我的解决方案。
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);
}
};