วิธีทดสอบโค้ดขึ้นอยู่กับตัวแปรสภาพแวดล้อมโดยใช้ JUnit


147

ฉันมีโค้ด Java ชิ้นหนึ่งซึ่งใช้ตัวแปรสภาพแวดล้อมและพฤติกรรมของโค้ดขึ้นอยู่กับค่าของตัวแปรนี้ ฉันต้องการทดสอบรหัสนี้ด้วยค่าตัวแปรสภาพแวดล้อมที่แตกต่างกัน ฉันจะทำสิ่งนี้ใน JUnit ได้อย่างไร?

ฉันเคยเห็นวิธีตั้งค่าตัวแปรสภาพแวดล้อมใน Javaโดยทั่วไปแล้ว แต่ฉันสนใจด้านการทดสอบหน่วยมากกว่าโดยเฉพาะอย่างยิ่งเมื่อพิจารณาว่าการทดสอบไม่ควรรบกวนซึ่งกันและกัน


เนื่องจากสิ่งนี้มีไว้สำหรับการทดสอบกฎการทดสอบหน่วยกฎของระบบอาจเป็นคำตอบที่ดีที่สุดในปัจจุบัน
Atifm

3
สำหรับผู้ที่สนใจคำถามเดียวกันขณะใช้ JUnit 5: stackoverflow.com/questions/46846503/…
Felipe Martins Melo

@FelipeMartinsMelo คำถามนั้นเกี่ยวกับคุณสมบัติของระบบไม่ใช่ตัวแปรสภาพแวดล้อม นี่คือโซลูชันที่เข้ากันได้กับ JUnit 5 สำหรับตัวแปรสภาพแวดล้อม: stackoverflow.com/a/63494695/3429133
beatngu13

คำตอบ:


204

Library System LambdaมีวิธีการwithEnvironmentVariablesตั้งค่าตัวแปรสภาพแวดล้อม

public void EnvironmentVariablesTest {
  @Test
  public void setEnvironmentVariable() {
    String value = withEnvironmentVariable("name", "value")
      .execute(() -> System.getenv("name"));
    assertEquals("value", value);
  }
}

สำหรับ Java 5-7 ห้องสมุดระบบกฎระเบียบมีกฎ JUnit EnvironmentVariablesที่เรียกว่า

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

คุณไม่จำเป็นต้อง ตัวแปรสภาพแวดล้อมดั้งเดิมจะถูกรีเซ็ตโดยอัตโนมัติโดยกฎหลังจากดำเนินการทดสอบทั้งหมดในคลาส
Stefan Birkner

2
import org.junit.contrib.java.lang.system.EnvironmentVariables;คุณจะต้องเพิ่มการอ้างอิงcom.github.stefanbirkner:system-rulesในโครงการของคุณ มีให้บริการใน MavenCentral
Jean Bob

2
นี่คือคำแนะนำในการเพิ่มการพึ่งพา: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier

1
Stefan ฟังดูยอดเยี่ยม แต่สิ่งนี้เปลี่ยนค่าของตัวแปรสภาพแวดล้อมสำหรับโค้ดภายใต้การทดสอบหรือไม่? ยกโทษให้ฉันหากคำตอบนั้นชัดเจน แต่คำตอบของคุณหรือการอ่าน SystemRules ดูเหมือนจะไม่ตอบคำถามของฉัน
CPerkins

82

วิธีแก้ปัญหาตามปกติคือการสร้างคลาสที่จัดการการเข้าถึงตัวแปรสภาพแวดล้อมซึ่งคุณสามารถเยาะเย้ยในคลาสทดสอบของคุณได้

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 () โดยตรง


3
ฉันรู้ว่าคำถามนี้เก่า แต่ฉันอยากจะบอกว่านี่เป็นคำตอบที่ถูกต้อง คำตอบที่ได้รับการยอมรับสนับสนุนการออกแบบที่ไม่ดีโดยมีการพึ่งพาที่ซ่อนอยู่ในระบบในขณะที่คำตอบนี้สนับสนุนให้มีการออกแบบที่เหมาะสมกับระบบซึ่งเป็นเพียงการพึ่งพาอื่นที่ควรได้รับการฉีดเข้าไป
แอนดรูว์

33

ในสถานการณ์ที่คล้ายกันเช่นนี้ที่ฉันต้องเขียนTest Caseซึ่งขึ้นอยู่กับEnvironment Variableฉันลองทำตาม:

  1. ฉันไปสำหรับกฎของระบบที่แนะนำโดยสเตฟานเบิร์คเนอร์ การใช้งานนั้นง่ายมาก แต่เร็วกว่านั้นฉันพบว่าพฤติกรรมเอาแน่เอานอนไม่ได้ ในการรันครั้งเดียวจะได้ผลในการรันครั้งถัดไปจะล้มเหลว ฉันตรวจสอบและพบว่ากฎของระบบทำงานได้ดีกับ JUnit 4 หรือเวอร์ชันที่สูงกว่า แต่ในกรณีของผมใช้ไหบางอย่างที่ขึ้นอยู่กับJUnit 3 ดังนั้นฉันจึงข้ามกฎของระบบไป เพิ่มเติมเกี่ยวกับมันคุณสามารถหาที่นี่คำอธิบายประกอบ @Rule ไม่ทำงานในขณะที่ใช้ TestSuite ใน JUnit
  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>

หลังจากการเปลี่ยนแปลงนี้ฉันเรียกใช้กรณีทดสอบอีกครั้งและทันใดนั้นทุกอย่างก็ทำงานตามที่คาดไว้ สำหรับข้อมูลของผู้อ่านผมสำรวจวิธีการนี้ในMaven 3.xดังนั้นฉันไม่มีความคิดในMaven 2.x


2
โซลูชันนี้ดีที่สุดและควรได้รับการยอมรับเนื่องจากคุณไม่ต้องการอะไรเพิ่มเติมเช่น lib Maven เพียงอย่างเดียวก็เพียงพอแล้ว ขอบคุณ @RLD
Semo

@Semo ต้องใช้ maven ซึ่งเป็นข้อกำหนดที่ใหญ่กว่าการใช้ lib มันจับคู่การทดสอบ Junit กับ pom และตอนนี้การทดสอบจะต้องดำเนินการจาก mvn เสมอแทนที่จะเรียกใช้โดยตรงบน IDE ตามปกติ
Chirlo

@Chirlo ขึ้นอยู่กับว่าคุณต้องการให้โปรแกรมของคุณผูกกับอะไร เมื่อใช้ Maven คุณสามารถกำหนดค่าได้ในที่เดียวและเขียนโค้ดที่เรียบร้อยและรัดกุม หากคุณใช้ไลบรารีคุณต้องเขียนโค้ดในหลาย ๆ ที่ เกี่ยวกับจุดที่คุณรัน JUnits คุณสามารถรัน JUnits จาก IDE เช่น Eclipse แม้ว่าคุณจะใช้ Maven
RLD

@RLD วิธีเดียวที่ฉันรู้ใน Eclipse จะเรียกใช้เป็นการกำหนดค่าการรัน 'Maven' แทนที่จะเป็น Junit ซึ่งยุ่งยากกว่ามากและมีเอาต์พุตข้อความแทนมุมมอง Junit ปกติ และฉันไม่ค่อยทำตามจุดที่คุณต้องการโค้ดที่เป็นระเบียบและรัดกุมและต้องเขียนโค้ดในหลาย ๆ ที่ สำหรับฉันการมีข้อมูลการทดสอบใน pom ที่ใช้ในการทดสอบ Junit นั้นคลุมเครือมากกว่าการใช้ร่วมกัน ฉันอยู่ในสถานการณ์นี้เมื่อไม่นานมานี้และลงเอยด้วยการทำตามแนวทางของ MathewFarwell ไม่จำเป็นต้องใช้ไลบรารี / เทคนิคปอมและทุกอย่างอยู่ร่วมกันในการทดสอบเดียวกัน
Chirlo

1
สิ่งนี้ทำให้ตัวแปรสภาพแวดล้อมเป็นฮาร์ดโค้ดและไม่สามารถเปลี่ยนแปลงได้จากการเรียกใช้ System.getenv ครั้งเดียวเป็นครั้งถัดไป แก้ไข?
Ian Stewart

13

ฉันคิดว่าวิธีที่สะอาดที่สุดในการทำเช่นนี้คือใช้ 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");
    }
}

10
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

แทนที่จะใช้เมื่อ (System.getenv ("FOO_VAR_1")) สามารถใช้ได้เมื่อ (System.getenv (eq ("FOO_VAR_1")) eq มาจาก org.mockito.Mockito.eq
Piyush Vishwakarma

10

ถอดรหัสรหัส Java จากตัวแปร Environment ที่ให้ตัวอ่านตัวแปรที่เป็นนามธรรมมากขึ้นซึ่งคุณใช้ EnvironmentVariableReader โค้ดของคุณเพื่อทดสอบการอ่าน

จากนั้นในการทดสอบของคุณคุณสามารถใช้โปรแกรมอ่านตัวแปรที่ให้ค่าการทดสอบของคุณได้

การฉีดยาแบบพึ่งพิงสามารถช่วยได้


9

คำตอบสำหรับคำถามนี้ฉันจะตั้งค่าตัวแปรสภาพแวดล้อมจาก Java ได้อย่างไร? ให้วิธีการแก้ไขแผนที่ (ไม่สามารถแก้ไขได้) ใน System.getenv () ดังนั้นแม้ว่าจะไม่เปลี่ยนค่าของตัวแปรสภาพแวดล้อมระบบปฏิบัติการ แต่ก็สามารถใช้สำหรับการทดสอบหน่วยได้เนื่องจากจะเปลี่ยนสิ่งที่System.getenvจะส่งคืน


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

1
คุณลืมบอกว่าคุณกำลังใช้ JMockit :) ไม่ว่าวิธีนี้จะใช้งานได้ดีกับ JUnit 5
Ryan J. McDonough

4

แม้ว่าฉันคิดว่าคำตอบนี้ดีที่สุดสำหรับโครงการ 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 <> (); คลาส <?> clazz = Class.forName ("java.lang.ProcessEnvironment"); ฟิลด์ theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); ฟิลด์ theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); ``
Intenex

4

สำหรับผู้ใช้ JUnit 4 System LambdaตามคำแนะนำของStefan Birknerนั้นเหมาะสมอย่างยิ่ง

ในกรณีที่คุณใช้ 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"));
}

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

-2

คุณสามารถใช้วิธีการตั้งค่า () เพื่อประกาศค่าต่างๆของ env ของคุณ ตัวแปรในค่าคงที่ จากนั้นใช้ค่าคงที่เหล่านี้ในวิธีการทดสอบที่ใช้ในการทดสอบสถานการณ์ต่างๆ


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

1
ประเด็นหลักคือไม่เข้าถึงตัวแปร env จากการทดสอบ junit
Tanmoy Bhattacharjee

-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"));
         }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.