Як перевірити код, залежний від змінних середовища, використовуючи JUnit?


139

У мене є фрагмент коду 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, мені потрібно скинути чи очистити його після використання, якщо так, то як?
Mritunjay

Вам не потрібно. Оригінальні змінні середовища автоматично скидаються правилом після того, як будуть виконані всі тести в класі.
Стефан Біркнер

Цей підхід працює лише для версії 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

У подібній ситуації, як ця, коли мені довелося написати тестовий випадок, який залежить від змінної середовища , я спробував наступне:

  1. Я пішов на Системні правила, як запропонував Стефан Біркнер . Його використання було простим. Але раніше, ніж пізніше, я виявив поведінку помилковою. В одному пробігу він працює, в наступному запуску він не вдається. Я дослідив і виявив, що Системні правила добре працюють з версією JUnit 4 або вище. Але в моїх випадках я використовував кілька банок, які залежали від JUnit 3 . Тому я пропустив Системні правила . Більше про це ви можете знайти тут @ Анотація Rule не працює під час використання TestSuite в JUnit .
  2. Далі я спробував створити змінну середовища через клас Process Builder, що надається 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>

Після цієї зміни я знову запустив тестові справи і раптом усі спрацювали так, як очікувалося. Для отримання інформації читача, я досліджував цей підхід в Maven 3.x , тому я поняття не маю про Maven 2.x .


2
Це рішення є найкращим, і воно повинно бути прийнятим, оскільки нічого додаткового, як ліб, вам не знадобиться. Сам Мейвен досить зручний. Спасибі @RLD
Semo

@Semo, але для цього потрібен Maven, що набагато більша вимога, ніж використання lib. Він з'єднує тест Junit з пом, і тест тепер завжди потрібно виконувати з mvn, а не запускати його безпосередньо на IDE звичайним способом.
Чирло

@Chirlo, це залежить від того, з чим ти хочеш пов'язати свою програму. Використовуючи Maven, ви можете налаштувати в одному місці і написати акуратний і стислий код. Якщо ви користуєтеся бібліотекою, вам доведеться писати код у декількох місцях. Щодо точки вашого запуску JUnits, ви можете запускати JUnits з IDE, як Eclipse, навіть якщо ви використовуєте Maven.
RLD

@RLD, єдиний спосіб, про який я знаю в Eclipse, це запускати його як конфігурацію запуску Maven замість Junit, який набагато громіздкіший і має лише вихід тексту замість звичайного подання Junit. І я не дуже дотримуюся вашої точки зору акуратного та стислого коду та необхідності писати код у кількох місцях. Для мене наявність тестових даних у пом, які потім використовуються в тесті 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());
}

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

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'.помилку
Андремоній

10

Розв’яжіть код Java від змінної Environment, надаючи більш абстрактний зчитувач змінних, який ви реалізуєте разом із EnvironmentVariableReader, щоб перевірити читання з.

Тоді у вашому тесті ви можете дати іншу реалізацію зчитувача змінної, яка забезпечує ваші тестові значення.

У цьому може допомогти ін'єкційна залежність.


9

Ця відповідь на питання Як встановити змінні середовища з Java? надає спосіб змінити (незмінювану) карту в System.getenv (). Тому, хоча воно НЕ РЕАЛЬНО змінює значення змінної середовища ОС, його можна використовувати для тестування одиниць, оскільки воно змінює те, що System.getenv поверне.


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

1
Ви забули згадати, що ви використовуєте JMockit. :) Незважаючи на це, це рішення також чудово працює з JUnit 5
Ryan J. McDonough

1

Незважаючи на те, що я вважаю, що ця відповідь найкраща для проектів 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 ("довкілля"); Поле theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); RemoveStaticFinalAndSetValue (theEnvironmentField, envMap); RemoveStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); `` `
Intenex

-2

Ну, ви можете використовувати метод setup (), щоб оголосити різні значення вашої env. змінні в константах. Потім використовуйте ці константи в методах тестів, які використовуються для тестування різного сценарію.


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

1
Головне - не звертатися до змінних env з тестування на Джуніт
Танмой Бхаттачарджі

-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"));
         }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.