Как проверить код, зависящий от переменных среды, используя JUnit?

140

У меня есть кусок кода Java, который использует переменную среды, и поведение кода зависит от значения этой переменной. Я хотел бы проверить этот код с различными значениями переменной среды. Как я могу сделать это в JUnit?

Я видел несколько способов установить переменные среды в Java в целом, но меня больше интересует аспект модульного тестирования, особенно учитывая, что тесты не должны мешать друг другу.

Витовт
источник
Так как это для тестирования, правило модульного тестирования Системных Правил может быть лучшим ответом в настоящее время.
Atifm
3
Просто для тех, кто интересуется тем же вопросом при использовании JUnit 5: stackoverflow.com/questions/46846503/…
Фелипе Мартинс Мело

Ответы:

199

Библиотека системных правил предоставляет правило 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"));
  }
}

Отказ от ответственности: я автор Системных Правил.

Стефан Биркнер
источник
1
Я использую это как @ClassRule, мне нужно сбросить или очистить его после использования, если да, то как?
Мритунджей,
Вам не нужно. Исходные переменные среды автоматически сбрасываются правилом после выполнения всех тестов в классе.
Стефан Биркнер
Этот подход работает только для JUnit 4 или более поздней версии. Не рекомендуется для JUnit 3 или более низкой версии , или если смешать JUnit 4 и JUnit 3.
RLD
2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Вам нужно будет добавить зависимость com.github.stefanbirkner:system-rulesв ваш проект. Это доступно в MavenCentral.
Жан Боб
2
Вот инструкции по добавлению зависимости: stefanbirkner.github.io/system-rules/download.html
Гильерме Гарнье,
77

Обычным решением является создание класса, который управляет доступом к этой переменной среды, которую вы затем можете смоделировать в своем тестовом классе.

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 ().

Мэтью Фарвелл
источник
1
Я знаю, что этот вопрос старый, но я хотел сказать, что это правильный ответ. Принятый ответ поощряет плохой дизайн со скрытой зависимостью от Системы, тогда как этот ответ поощряет надлежащий дизайн, рассматривающий Систему как еще одну зависимость, которая должна быть внедрена.
Андрей
30

В подобной ситуации, как это, где я должен был написать Test Case, который зависит от переменной среды , я попытался сделать следующее:

  1. Я пошел за Системными Правилами, как предложил Стефан Биркнер . Его использование было простым. Но раньше, чем позже, я нашел поведение странным. В одном запуске это работает, в следующем запуске это не удается. Я исследовал и обнаружил, что системные правила хорошо работают с JUnit 4 или более поздней версией. Но в моих случаях я использовал несколько банок, которые зависели от JUnit 3 . Поэтому я пропустил Системные правила . Подробнее об этом вы можете узнать здесь. @Rule аннотация не работает при использовании TestSuite в JUnit .
  2. Затем я попытался создать переменную среды через класс Process Builder, предоставляемый Java . Здесь с помощью Java Code мы можем создать переменную окружения, но вам нужно знать имя процесса или программы, чего я не знал . Также он создает переменную среды для дочернего процесса, а не для основного процесса.

Я потратил впустую день, используя два вышеупомянутых подхода, но безрезультатно. Тогда Мейвен пришел мне на помощь. Мы можем установить переменные среды или системные свойства через POM- файл Maven, который я считаю лучшим способом выполнить модульное тестирование для проекта на основе 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>

После этого изменения я снова запустил Test Cases и вдруг все заработало как положено. Для получения информации читателя, я исследовал этот подход в Maven 3.x , поэтому я понятия не имею о Maven 2.x .

RLD
источник
2
Это лучшее решение, и оно должно быть принято, потому что вам не нужно ничего дополнительного, например, lib. Один Maven достаточно удобен. Спасибо @RLD
Semo
@Semo требует maven, что гораздо больше, чем использование lib. Он соединяет тест Junit с pom, и теперь тест всегда нужно выполнять из mvn вместо того, чтобы запускать его напрямую в IDE обычным способом.
Чирло
@ Чирло, это зависит от того, с чем вы хотите связать свою программу. Используя Maven, вы можете настроить в одном месте и написать аккуратный и лаконичный код. Если вы используете библиотеку, вы должны написать код в нескольких местах. Что касается вашей цели запуска JUnits, вы можете запускать JUnits из IDE, например, Eclipse, даже если вы используете Maven.
RLD
@RLD, единственный способ, которым я знаю в Eclipse, - запускать его как конфигурацию запуска «Maven» вместо Junit, который намного более громоздок и просто выводит текст вместо обычного представления Junit. И я не совсем понимаю вашу точку зрения аккуратного и лаконичного кода и необходимости писать код в нескольких местах. Для меня наличие тестовых данных в pom, которые затем используются в тесте Junit, более неясно, чем их совместное использование. Недавно я оказался в такой ситуации и в итоге следовал подходу MathewFarwell: мне не нужны библиотеки / трюки с помпами, и все в одном тесте.
Чирло
1
Это делает переменные среды жестко запрограммированными, и их нельзя изменить от одного вызова System.getenv к другому. Верный?
Ян Стюарт
12

Я думаю, что самый чистый способ сделать это с 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());
}
pgkelley
источник
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
    }
}

Вы можете сделать следующее:

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");
    }
}
Mangusta
источник
8
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'.ошибку
Andremoniy
10

Отсоедините код Java от переменной Environment, предоставляя более абстрактное средство чтения переменных, которое вы реализуете с помощью EnvironmentVariableReader вашего кода для тестирования чтений.

Затем в вашем тесте вы можете дать другую реализацию считывателя переменных, которая предоставляет ваши тестовые значения.

Инъекция зависимости может помочь в этом.

Андреа Коллеони
источник
9

Это ответ на вопрос Как установить переменные окружения из Java? предоставляет способ изменить (неизменяемую) карту в System.getenv (). Таким образом, хотя он НЕ ДЕЙСТВИТЕЛЬНО изменяет значение переменной среды ОС, он может использоваться для модульного тестирования, поскольку он изменяет то, что System.getenv возвращает.

sudocode
источник
4

Надеюсь, что проблема решена. Я просто подумал, чтобы сказать свое решение.

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);
        }
    };
Muthu
источник
1
Вы забыли упомянуть, что вы используете JMockit. :) Несмотря на это, это решение также прекрасно работает с JUnit 5
Райан Макдоно
2

Несмотря на то, что я думаю, что этот ответ является лучшим для проектов 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);
    }
}
Джордж З.
источник
Спасибо за это! Моя версия Java, похоже, не имеет, theCaseInsensitiveEnvironmentа вместо этого имеет поле theEnvironment, как показано ниже: `` `envMap = new HashMap <> (); Class <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Поле theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); Поле theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); `` `
Intenex
-2

Ну, вы можете использовать метод setup (), чтобы объявить различные значения вашего env. переменные в константах. Затем используйте эти константы в методах испытаний, используемых для проверки другого сценария.

Cygnusx1
источник
-2

Если вы хотите получить информацию о переменной среды в 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); 
     }   
 }
Dimitri
источник
1
Главное не в том, чтобы не получить доступ к переменным env из теста
junit
-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"));
         }
}
ejaenv
источник