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

vit*_*aut 113 java testing junit unit-testing environment-variables

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

我已经看到了一些在Java设置环境变量的方法,但是我对它的单元测试方面更感兴趣,特别是考虑到测试不应该相互干扰.

Ste*_*ner 164

系统规则提供了用于设置环境变量的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"));
  }
}
Run Code Online (Sandbox Code Playgroud)

免责声明:我是系统规则的作者.

  • `import org.junit.contrib.java.lang.system.EnvironmentVariables;` 你需要在你的项目中添加依赖`com.github.stefanbirkner:system-rules`。它在 MavenCentral 中可用。 (2认同)
  • 以下是添加依赖项的说明:https://stefanbirkner.github.io/system-rules/download.html (2认同)

Mat*_*ell 64

通常的解决方案是创建一个管理对此环境变量的访问的类,然后可以在测试类中进行模拟.

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,受测试的类使用Environment类获取环境变量,而不是直接从System.getenv()获取.

  • 我知道这个问题很老了,但我想说这是正确的答案。接受的答案鼓励对系统隐藏依赖的不良设计,而这个答案鼓励正确的设计,将系统视为应该注入的另一个依赖项。 (13认同)

RLD*_*RLD 18

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

  1. 我按照Stefan Birkner的建议去了系统规则.它的使用很简单.但不久之后,我发现这种行为不稳定.在一次运行中,它可以工作,在下一次运行中它失败了.我调查并发现系统规则适用于JUnit 4或更高版本.但在我的情况下,我使用的是一些依赖于JUnit 3的 Jars .所以我跳过了系统规则.更多信息,你可以在这里找到@Rule注释在JUnit中使用TestSuite时不起作用.
  2. 接下来,我试图创建环境变量,通过进程生成提供一流的Java.通过Java Code,我们可以创建一个环境变量,但是您需要知道我没有的过程程序名称.它还为子进程创建环境变量,而不是为主进程创建环境变量.

我使用上述两种方法浪费了一天,但无济于事.然后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>
Run Code Online (Sandbox Code Playgroud)

在此更改之后,我再次运行测试用例,突然所有工作都按预期工作.对于读者的信息,我在Maven 3.x中探索了这种方法,所以我对Maven 2.x一无所知.

  • 这使得环境变量被硬编码,并且它们不能从一次 System.getenv 调用到下一次调用进行更改。正确的? (3认同)
  • 这个解决方案是最好的,应该是被接受的解决方案,因为您不需要任何额外的东西,比如库。Maven 本身就足够方便了。谢谢@RLD (2认同)
  • @Semo它需要maven,这比使用lib的要求要大得多。它将 Junit Test 耦合到 pom,现在测试总是需要从 mvn 执行,而不是像通常的方式一样直接在 IDE 上运行。 (2认同)

小智 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
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以执行以下操作:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • `when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1")`导致`org.mockito.exceptions.misusing.MissingMethodInvocationException:when()需要一个必须为'a的参数方法调用模拟错误 (6认同)
  • 当人们显示导入而不是假设它们太明显而无法添加时,我真的很感激。很多时候,一个类出现在多个包中,使得选择变得不那么明显。 (2认同)

And*_*oni 9

将Java代码与Environment变量分离,从而提供一个更抽象的变量读取器,您可以使用EnvironmentVariableReader实现您的代码来测试读取.

然后在测试中,您可以为变量阅读器提供不同的实现,以提供您的测试值.

依赖注入可以帮助解决这个问题.


bea*_*u13 9

对于 JUnit 4 用户,Stefan Birkner建议的System Lambda非常适合。

如果您使用的是 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"));
}
Run Code Online (Sandbox Code Playgroud)


sud*_*ode 8

这个答案的问题如何设置环境变量从Java?提供了一种在System.getenv()中更改(不可修改的)Map的方法.因此,虽然它并没有真正改变OS环境变量的值,但它可以用于单元测试,因为它确实改变了System.getenv将返回的内容.


pgk*_*ley 5

我认为最简单的方法是使用Mockito.spy()。它比创建单独的类进行模拟和传递要轻巧一些。

将获取环境变量移至另一种方法:

@VisibleForTesting
String getEnvironmentVariable(String envVar) {
    return System.getenv(envVar);
}
Run Code Online (Sandbox Code Playgroud)

现在在您的单元测试中执行以下操作:

@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());
}
Run Code Online (Sandbox Code Playgroud)


小智 5

希望问题得到解决。我只是想告诉我的解决方案。

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);
        }
    };
Run Code Online (Sandbox Code Playgroud)

  • 您忘了提及您正在使用 JMockit。:) 无论如何,这个解决方案也适用于 JUnit 5 (2认同)

Geo*_* Z. 5

尽管我认为这个答案最适合 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);
    }
}
Run Code Online (Sandbox Code Playgroud)