如何使用JUnit测试依赖于环境变量的代码?

时间:2011-11-17 14:36:20

标签: java unit-testing testing junit environment-variables

我有一段使用环境变量的Java代码,代码的行为取决于此变量的值。我想用环境变量的不同值测试此代码。我怎么能在JUnit中做到这一点?

我一般都看过some ways to set environment variables in Java,但我对它的单元测试方面更感兴趣,特别是考虑到测试不应该相互干扰。

18 个答案:

答案 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)

在类似的情况下,我必须编写依赖于环境变量测试用例,我尝试了以下操作:

  1. 我按照 Stefan Birkner 的建议去了系统规则。它的使用很简单。但不久之后,我发现这种行为不稳定。在一次运行中,它可以工作,在下一次运行中它失败了。 我调查并发现系统规则适用于JUnit 4或更高版本。但在我的情况下,我使用的是一些依赖于 JUnit 3 的Jars。所以我跳过系统规则。更多信息,请访问@Rule annotation doesn't work while using TestSuite in JUnit
  2. 接下来,我尝试通过 Java 提供的 Process Builder 类创建环境变量。通过Java Code,我们可以创建一个环境变量,但您需要知道我没有的进程程序名称。它还为子进程创建环境变量,而不是为主进程创建环境变量。
  3. 我使用上述两种方法浪费了一天,但无济于事。然后 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)

问题This answer

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

更多详情参考
https://www.baeldung.com/java-system-stubs

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