我有一段使用环境变量的Java代码,代码的行为取决于此变量的值。我想用环境变量的不同值测试此代码。我怎么能在JUnit中做到这一点?
我一般都看过some ways to set environment variables in Java,但我对它的单元测试方面更感兴趣,特别是考虑到测试不应该相互干扰。
答案 0 :(得分:127)
库System Rules提供了一个用于设置环境变量的JUnit规则。
import org.junit.contrib.java.lang.system.EnvironmentVariables;
public void EnvironmentVariablesTest {
@Rule
public final EnvironmentVariables environmentVariables
= new EnvironmentVariables();
@Test
public void setEnvironmentVariable() {
environmentVariables.set("name", "value");
assertEquals("value", System.getenv("name"));
}
}
免责声明:我是系统规则的作者。
答案 1 :(得分:60)
通常的解决方案是创建一个管理对此环境变量的访问的类,然后可以在测试类中进行模拟。
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类获取环境变量,而不是直接从System.getenv()获取。
答案 2 :(得分:14)
在类似的情况下,我必须编写依赖于环境变量的测试用例,我尝试了以下操作:
我使用上述两种方法浪费了一天,但无济于事。然后 Maven 来救我。我们可以通过 Maven POM 文件设置环境变量或系统属性,我认为最好的方法是单元测试基于 Maven 的项目。以下是我在 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 。
答案 3 :(得分:11)
我不认为这已被提及,但您也可以使用 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");
}
}
答案 4 :(得分:7)
How do I set environment variables from Java?提供了一种在System.getenv()中改变(不可修改的)Map的方法。因此,虽然它并没有真正改变OS环境变量的值,但它可以用于单元测试,因为它确实会改变System.getenv将返回的内容。
答案 5 :(得分:7)
将Java代码与Environment变量分离,从而提供一个更抽象的变量读取器,您可以通过EnvironmentVariableReader实现代码来测试读取。
然后在测试中,您可以提供变量阅读器的不同实现,以提供您的测试值。
依赖注入可以帮助解决这个问题。
答案 6 :(得分:6)
对于JUnit 4用户,System Lambda建议的Stefan Birkner非常适合。
如果您使用的是JUnit 5,则有JUnit Pioneer扩展包。它带有@ClearEnvironmentVariable
和@SetEnvironmentVariable
。来自docs:
@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"));
}
答案 7 :(得分:5)
我认为最简洁的方法是使用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());
}
答案 8 :(得分:5)
即使我认为this answer最适合Maven项目,也可以通过反射来实现(在 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);
}
}
答案 9 :(得分:2)
希望问题已解决。我只是想告诉我解决方法。
real*8
答案 10 :(得分:0)
简单
添加下面的maven依赖
<!-- for JUnit 4 -->
<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>system-stubs-junit4</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
<!-- for JUnit 5 -->
<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>system-stubs-jupiter</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
在你的测试中,你可以使用类似的东西:
@Rule
public EnvironmentVariablesRule environmentVariablesRule = new EnvironmentVariablesRule();
@Test
public void givenEnvironmentCanBeModified_whenSetEnvironment_thenItIsSet() {
// mock that the system contains an environment variable "ENV_VAR" having value "value1"
environmentVariablesRule.set("ENV_VAR", "value1");
assertThat(System.getenv("ENV_VAR")).isEqualTo("value1");
}
答案 11 :(得分:0)
一种缓慢、可靠、老派的方法,始终适用于每种语言(甚至语言之间)的每个操作系统是将您需要的“系统/环境”数据写入临时文本文件,在您需要时读取它需要它,然后删除它。当然,如果您并行运行,那么您需要为文件提供唯一名称,如果您将敏感信息放入其中,则需要对其进行加密。
答案 12 :(得分:0)
以上建议的重点是发明在运行时传递变量、设置它们和清除它们等等的方法。但是为了“从结构上”测试事物,我猜您想为不同的场景使用不同的测试套件?就像您想运行“较重”的集成测试构建一样,而在大多数情况下,您只想跳过它们。但是,您不会尝试“发明在运行时设置内容的方法”,而只是告诉 Maven 您想要什么?曾经有很多工作告诉 Maven 通过配置文件运行特定的测试,如果你谷歌周围的人会建议通过 springboot 来做(但如果你没有将 springboot monstrum 拖入你的项目,这似乎是一个可怕的'只运行 JUnits' 的足迹,对吗?)。否则,它会暗示大量或多或少不方便的 POM XML 杂耍,这也很烦人,我们只能说,“九十年代的举动”,就像仍然坚持“用 XML 制作春豆”一样不方便,炫耀你的 < em>ultimate 600 行 logback.xml 或诸如此类...?
现在,您可以只使用 Junit 5(此示例适用于 Maven,更多详细信息可在此处找到 JUnit 5 User Guide 5)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
然后在您最喜欢的实用程序库中创建一个简单的漂亮注释类,例如
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@EnabledIfEnvironmentVariable(named = "MAVEN_CMD_LINE_ARGS", matches = "(.*)integration-testing(.*)")
public @interface IntegrationTest {}
因此,每当您的 cmdline 选项包含 -Pintegration-testing 时,您的 @IntegrationTest 注释测试类/方法才会触发。或者,如果您不想使用(和设置)特定的 Maven 配置文件,而只想通过
传递“触发”系统属性mvn <cmds> -DmySystemPop=mySystemPropValue
并调整您的注释界面以触发它(是的,还有一个@EnabledIfSystemProperty)。或者确保你的 shell 被设置为包含“你需要的任何东西”,或者,正如上面所建议的那样,通过你的 POM XML 添加系统环境。
让您的代码在内部在运行时摆弄 env 或模拟 env,设置它然后可能“清除”运行时 env 以在执行期间更改自身,这似乎是一种糟糕的,甚至可能是危险的方法- 很容易想象有人迟早会犯一个“隐藏的”内部错误,这个错误会暂时被忽视,只是突然出现并在以后的生产中狠狠地咬你......?您通常更喜欢这样一种方法,即“给定的输入”给出“预期的输出”,随着时间的推移很容易掌握和维护,您的编码同事会“立即”看到它。
好长的“答案”,或者更确切地说是关于为什么您更喜欢这种方法的意见(是的,起初我只是阅读了这个问题的标题并继续回答了这个问题,即“如何测试代码依赖于使用 JUnit 的环境变量').
答案 13 :(得分:0)
库https://github.com/webcompere/system-stubs/tree/master/system-stubs-jupiter-string body_string = "my test <result> is inside";
var reg = new Regex(string.Format("(?<={0})(.+?)(?={1})", Regex.Escape("<"), Regex.Escape(">")));
var result = reg.Match(body_string).ToString();
的分支-提供了JUnit 5插件:
import functools
def common_filtering(year, purpose, market):
df = df_london.copy()
filters = []
if year is not ALL:
filters.append(df['year']==year)
if purpose is not ALL:
filters.append(df['purpose']==purpose)
if market is not ALL:
filters.append(df['market']==market)
output.clear_output()
with output:
if filters:
df_filter = functools.reduce(lambda x,y: x&y, filters)
display(df.loc[df_filter])
else:
display(df)
def dropdown_year_eventhandler(change):
common_filtering(change.new, dropdown_purpose.value, dropdown_market.value)
def dropdown_purpose_eventhandler(change):
common_filtering(dropdown_year.value, change.new, dropdown_market.value,)
def dropdown_market_eventhandler(change):
common_filtering(dropdown_year.value, dropdown_purpose.value, change.new)
https://junit-pioneer.org/的替代方案要求在编译时知道环境变量的值。上面还支持设置
system-lambda
中的环境变量,这意味着它可以与@ExtendWith(SystemStubsExtension.class)
class SomeTest {
@SystemStub
private EnvironmentVariables environmentVariables =
new EnvironmentVariables("name", "value");
@Test
void someTest() {
// environment is set here
// can set a new value into the environment too
environmentVariables.set("other", "value");
// tidy up happens at end of this test
}
}
之类的东西很好地互操作,这些东西可能会设置子测试所需的某些资源。
答案 14 :(得分:0)
您可以使用Powermock模拟呼叫。喜欢:
PowerMockito.mockStatic(System.class);
PowerMockito.when(System.getenv("MyEnvVariable")).thenReturn("DesiredValue");
您还可以使用以下方法模拟所有呼叫:
PowerMockito.mockStatic(System.class);
PowerMockito.when(System.getenv(Mockito.anyString())).thenReturn(envVariable);
答案 15 :(得分:-1)
您可以使用setup()方法声明env的不同值。常量中的变量。然后在用于测试不同场景的测试方法中使用这些常量。
答案 16 :(得分:-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"));
}
}
答案 17 :(得分:-2)
如果要在Java中检索有关环境变量的信息,可以调用方法:System.getenv();
。作为属性,此方法返回一个Map,其中包含变量名称作为键,变量值作为映射值。这是一个例子:
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);
}
}