Làm cách nào để kiểm tra mã phụ thuộc vào các biến môi trường bằng JUnit?


138

Tôi có một đoạn mã Java sử dụng biến môi trường và hành vi của mã phụ thuộc vào giá trị của biến này. Tôi muốn kiểm tra mã này với các giá trị khác nhau của biến môi trường. Làm thế nào tôi có thể làm điều này trong JUnit?

Tôi đã thấy một số cách để đặt các biến môi trường trong Java nói chung, nhưng tôi quan tâm nhiều hơn đến khía cạnh thử nghiệm đơn vị của nó, đặc biệt là xem xét rằng các thử nghiệm không nên can thiệp lẫn nhau.


Vì đây là để thử nghiệm, quy tắc kiểm tra đơn vị Quy tắc hệ thống có thể là câu trả lời tốt nhất hiện tại.
Atifm

3
Chỉ dành cho những người quan tâm đến cùng một câu hỏi trong khi sử dụng JUnit 5: stackoverflow.com/questions/46846503/ Khăn
Felipe Martins Melo

Câu trả lời:


198

Quy tắc hệ thống thư viện cung cấp Quy tắc JUnit để đặt các biến môi trường.

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

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả của Quy tắc hệ thống.


1
Tôi đang sử dụng cái này với tên @ClassRule, tôi có cần thiết lập lại hoặc xóa nó sau khi sử dụng không, nếu có thì làm thế nào?
Mritunjay

Bạn không cần. Các biến môi trường ban đầu được tự động đặt lại theo quy tắc sau khi tất cả các kiểm tra trong lớp được thực thi.
Stefan Birkner

Cách tiếp cận này chỉ hoạt động cho phiên bản JUnit 4 trở lên. Không được đề xuất cho JUnit 3 hoặc phiên bản thấp hơn hoặc nếu bạn trộn JUnit 4 và JUnit 3.
RLD

2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Bạn sẽ cần thêm sự phụ thuộc com.github.stefanbirkner:system-rulestrong dự án của bạn. Nó có sẵn trong MavenCentral.
Jean Bob

2
Dưới đây là các hướng dẫn để thêm phụ thuộc: stefanbirkner.github.io/system-rules/doad.html
Guilherme Garnier

76

Giải pháp thông thường là tạo một lớp quản lý quyền truy cập vào biến môi trường này, sau đó bạn có thể giả định trong lớp thử nghiệm của mình.

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

Lớp được kiểm tra sau đó lấy biến môi trường bằng lớp Môi trường, không phải trực tiếp từ System.getenv ().


1
Tôi biết câu hỏi này đã cũ, nhưng tôi muốn nói rằng đây là câu trả lời đúng. Câu trả lời được chấp nhận khuyến khích thiết kế kém với sự phụ thuộc ẩn vào Hệ thống, trong khi câu trả lời này khuyến khích Hệ thống xử lý thiết kế phù hợp như một phụ thuộc khác cần được đưa vào.
Andrew

30

Trong một tình huống tương tự như thế này khi tôi phải viết Test Case phụ thuộc vào Biến môi trường , tôi đã thử như sau:

  1. Tôi đã đi theo Quy tắc hệ thống theo đề xuất của Stefan Birkner . Công dụng của nó rất đơn giản. Nhưng sớm hơn sau đó, tôi thấy hành vi thất thường. Trong một lần chạy, nó hoạt động, trong lần chạy tiếp theo, nó thất bại. Tôi đã điều tra và thấy rằng System Rules hoạt động tốt với JUnit 4 hoặc phiên bản cao hơn. Nhưng trong trường hợp của tôi, tôi đã sử dụng một số Jars phụ thuộc vào JUnit 3 . Vì vậy, tôi đã bỏ qua Quy tắc hệ thống . Thông tin thêm về nó bạn có thể tìm thấy ở đây @Rule annotation không hoạt động trong khi sử dụng TestSuite trong JUnit .
  2. Tiếp theo, tôi đã cố gắng tạo Biến môi trường thông qua lớp Process Builder do Java cung cấp . Ở đây thông qua Mã Java, chúng ta có thể tạo một biến môi trường, nhưng bạn cần biết quy trình hoặc tên chương trình mà tôi đã không làm. Ngoài ra, nó tạo ra biến môi trường cho quá trình con, không phải cho quá trình chính.

Tôi đã lãng phí một ngày bằng hai cách tiếp cận trên, nhưng không có kết quả. Sau đó Maven đến giải cứu tôi. Chúng ta có thể đặt Biến môi trường hoặc Thuộc tính hệ thống thông qua tệp Maven POM mà tôi nghĩ cách tốt nhất để thực hiện Kiểm tra đơn vị cho dự án dựa trên Maven . Dưới đây là mục tôi đã thực hiện trong tệp 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>

Sau khi thay đổi, tôi chạy lại Test Case và đột nhiên tất cả đều hoạt động như mong đợi. Để biết thông tin của người đọc, tôi khám phá phương pháp này trong Maven 3.x , vì vậy tôi không có ý tưởng về Maven 2.x .


2
Giải pháp này là tốt nhất và nên được chấp nhận, bởi vì bạn sẽ không cần thêm bất cứ thứ gì như lib. Maven một mình là đủ tiện dụng. Cảm ơn bạn @RLD
Semo

@Semo nó yêu cầu maven mặc dù, đó là yêu cầu lớn hơn nhiều so với sử dụng lib. Nó kết hợp thử nghiệm Junit với pom và thử nghiệm bây giờ luôn cần được thực hiện từ mvn, thay vì chạy trực tiếp trên IDE theo cách thông thường.
Chirlo

@Chirlo, nó phụ thuộc vào những gì bạn muốn chương trình của bạn gắn với. Sử dụng Maven, bạn có thể định cấu hình ở một nơi và viết mã gọn gàng và súc tích. Nếu bạn sử dụng thư viện, bạn phải viết mã ở nhiều nơi. Về quan điểm của bạn khi chạy JUnits, bạn có thể chạy JUnits từ IDE như Eclipse ngay cả khi bạn sử dụng Maven.
RLD

@RLD, cách duy nhất tôi biết trong Eclipse sẽ chạy nó dưới dạng cấu hình chạy 'Maven' thay vì Junit, nó cồng kềnh hơn nhiều và chỉ có đầu ra văn bản thay vì chế độ xem Junit thông thường. Và tôi không hoàn toàn làm theo quan điểm của bạn về mã gọn gàng và súc tích và phải viết mã ở nhiều nơi. Đối với tôi, việc có dữ liệu thử nghiệm trong pom sau đó được sử dụng trong thử nghiệm Junit khó hiểu hơn so với việc kết hợp chúng với nhau. Gần đây tôi đã ở trong tình huống này và đã kết thúc theo cách tiếp cận của MathewFarwell, không cần thư viện / thủ thuật pom và mọi thứ đều nằm trong cùng một bài kiểm tra.
Chirlo

1
Điều này làm cho các biến môi trường được mã hóa cứng và chúng không thể được thay đổi từ một lần gọi System.getenv sang lần tiếp theo. Chính xác?
Ian Stewart

12

Tôi nghĩ rằng cách sạch nhất để làm điều này là với Mockito.spy (). Nó nhẹ hơn một chút so với việc tạo một lớp riêng để giả và vượt qua.

Di chuyển biến môi trường của bạn tìm nạp sang phương thức khác:

@VisibleForTesting
String getEnvironmentVariable(String envVar) {
    return System.getenv(envVar);
}

Bây giờ trong bài kiểm tra đơn vị của bạn làm điều này:

@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

Tôi không nghĩ rằng điều này đã được đề cập, nhưng bạn cũng có thể sử dụng Powermockito :

Được:

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

Bạn có thể làm như sau:

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")gây ra org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.lỗi
Andremoniy

10

Tách mã Java khỏi biến Môi trường cung cấp trình đọc biến trừu tượng hơn mà bạn nhận ra với môi trườngVariableReader mã của bạn để kiểm tra đọc từ đó.

Sau đó, trong thử nghiệm của bạn, bạn có thể đưa ra một triển khai khác của trình đọc biến cung cấp các giá trị thử nghiệm của bạn.

Phụ thuộc tiêm có thể giúp trong việc này.



4

Hy vọng vấn đề được giải quyết. Tôi chỉ nghĩ để nói với giải pháp của tôi.

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
Bạn đã quên đề cập rằng bạn đang sử dụng JMockit. :) Bất kể, giải pháp này cũng hoạt động tuyệt vời với JUnit 5
Ryan J. McDonough

1

Mặc dù tôi nghĩ rằng câu trả lời này là tốt nhất cho các dự án Maven, nhưng nó cũng có thể đạt được thông qua phản ánh (được thử nghiệm trong 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);
    }
}

Cảm ơn vì điều đó! Phiên bản Java của tôi dường như không có theCaseInsensitiveEnvironmentvà thay vào đó có một trường theEnvironment, như sau: `` `envMap = new HashMap <> (); Lớp <?> Clazz = Class.forName ("java.lang.ProcessEn Môi trường"); Trường theEn MôiField = clazz.getDeclaredField ("môi trường"); Trường theUnmodifiableEn MôiField = clazz.getDeclaredField ("theUnmodifiableEn Môi trường"); removeStaticFinalAndSetValue (theEn MôiField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEn MôiField, envMap); `` `
Intenex

-2

Vâng, bạn có thể sử dụng phương thức setup () để khai báo các giá trị khác nhau của env của bạn. các biến trong hằng số. Sau đó sử dụng các hằng số này trong các phương thức thử nghiệm được sử dụng để kiểm tra kịch bản khác nhau.


-2

Nếu bạn muốn lấy thông tin về biến môi trường trong Java, bạn có thể gọi phương thức : System.getenv();. Là các thuộc tính, phương thức này trả về một Bản đồ chứa các tên biến là các khóa và các giá trị biến là các giá trị bản đồ. Đây là một ví dụ :

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

Phương pháp getEnv()cũng có thể có một đối số. Ví dụ :

String myvalue = System.getEnv("MY_VARIABLE");

Để thử nghiệm, tôi sẽ làm một cái gì đó như thế này:

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
Điểm chính là không truy cập vào các biến env từ bài kiểm tra
Junit

-2

Tôi sử dụng System.getEnv () để lấy bản đồ và tôi giữ làm trường, vì vậy tôi có thể chế giễu nó:

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"));
         }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.