Bagaimana cara menguji kode tergantung pada variabel lingkungan menggunakan JUnit?

140

Saya memiliki sepotong kode Java yang menggunakan variabel lingkungan dan perilaku kode tergantung pada nilai variabel ini. Saya ingin menguji kode ini dengan nilai yang berbeda dari variabel lingkungan. Bagaimana saya bisa melakukan ini di JUnit?

Saya telah melihat beberapa cara untuk mengatur variabel lingkungan di Jawa secara umum, tetapi saya lebih tertarik pada aspek pengujian unit, terutama mengingat bahwa tes tidak boleh saling mengganggu.

riwayat hidup
sumber
Karena ini untuk pengujian, aturan pengujian unit Aturan Sistem mungkin merupakan jawaban terbaik saat ini.
Atifm
3
Hanya untuk mereka yang tertarik pada pertanyaan yang sama saat menggunakan JUnit 5: stackoverflow.com/questions/46846503/…
Felipe Martins Melo

Jawaban:

199

Pustaka Aturan Sistem memberikan Aturan JUnit untuk mengatur variabel lingkungan.

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

Penafian: Saya penulis Aturan Sistem.

Stefan Birkner
sumber
1
Saya menggunakan ini sebagai @ClassRule, apakah saya perlu mengatur ulang atau menghapusnya setelah digunakan, jika ya lalu bagaimana?
Mritunjay
Anda tidak perlu melakukannya. Variabel lingkungan asli secara otomatis direset oleh aturan setelah semua tes di kelas dieksekusi.
Stefan Birkner
Pendekatan ini hanya berfungsi untuk JUnit 4 atau versi yang lebih tinggi. Tidak direkomendasikan untuk JUnit 3 atau versi yang lebih rendah atau jika Anda mencampur JUnit 4 dan JUnit 3.
RLD
2
import org.junit.contrib.java.lang.system.EnvironmentVariables;Anda perlu menambahkan ketergantungan com.github.stefanbirkner:system-rulespada proyek Anda. Ini tersedia di MavenCentral.
Jean Bob
2
Berikut adalah instruksi untuk menambahkan dependensi: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier
77

Solusi yang biasa adalah membuat kelas yang mengelola akses ke variabel lingkungan ini, yang kemudian dapat Anda tiru di kelas tes Anda.

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

Kelas yang diuji kemudian mendapatkan variabel lingkungan menggunakan kelas Lingkungan, bukan langsung dari System.getenv ().

Matthew Farwell
sumber
1
Saya tahu pertanyaan ini sudah lama, tetapi saya ingin mengatakan bahwa ini adalah jawaban yang benar. Jawaban yang diterima mendorong desain yang buruk dengan ketergantungan tersembunyi pada Sistem, sedangkan jawaban ini mendorong desain yang memperlakukan Sistem hanya sebagai ketergantungan lain yang harus disuntikkan.
Andrew
30

Dalam situasi yang sama seperti ini di mana saya harus menulis Test Case yang bergantung pada Variabel Lingkungan , saya mencoba sebagai berikut:

  1. Saya mengikuti Aturan Sistem seperti yang disarankan oleh Stefan Birkner . Penggunaannya sederhana. Tetapi lebih cepat daripada kemudian, saya menemukan perilaku itu tidak menentu. Dalam satu kali menjalankan, ini berfungsi, dalam menjalankan berikutnya gagal. Saya menyelidiki dan menemukan bahwa Aturan Sistem berfungsi dengan baik dengan JUnit 4 atau versi yang lebih tinggi. Tetapi dalam kasus saya, saya menggunakan beberapa Jars yang tergantung pada JUnit 3 . Jadi saya melewatkan Aturan Sistem . Lebih lanjut tentang ini Anda dapat menemukan di sini @Rule annotation tidak berfungsi saat menggunakan TestSuite di JUnit .
  2. Selanjutnya saya mencoba membuat Variabel Lingkungan melalui kelas Process Builder yang disediakan oleh Java . Di sini melalui Java Code kita dapat membuat variabel lingkungan, tetapi Anda perlu mengetahui proses atau nama program yang tidak saya lakukan. Juga menciptakan variabel lingkungan untuk proses anak, bukan untuk proses utama.

Saya menghabiskan satu hari menggunakan dua pendekatan di atas, tetapi tidak berhasil. Lalu Maven datang untuk menyelamatkanku. Kita dapat mengatur Variabel Lingkungan atau Properti Sistem melalui file POM Maven yang saya pikir cara terbaik untuk melakukan Pengujian Unit untuk proyek berbasis Maven . Di bawah ini adalah entri yang saya buat di file 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>

Setelah perubahan ini, saya menjalankan Uji Kasus lagi dan tiba-tiba semua berfungsi seperti yang diharapkan. Untuk informasi pembaca, saya menjelajahi pendekatan ini dalam Maven 3.x , jadi saya tidak tahu pada Maven 2.x .

RLD
sumber
2
Solusi ini adalah yang terbaik dan harus yang diterima, karena Anda tidak akan memerlukan tambahan apa pun seperti lib. Maven sendiri cukup berguna. Terima kasih @RLD
Semo
@Semo membutuhkan maven, yang merupakan persyaratan yang jauh lebih besar daripada menggunakan lib. Ini memasangkan Tes Junit ke pom, dan tes sekarang selalu perlu dieksekusi dari mvn, daripada menjalankannya langsung pada IDE dengan cara biasa.
Chirlo
@ Chlolo, itu tergantung pada apa yang Anda inginkan untuk mengikat program Anda. Menggunakan Maven, Anda dapat mengonfigurasi di satu tempat dan menulis kode yang rapi dan ringkas. Jika Anda menggunakan perpustakaan, Anda harus menulis kode di banyak tempat. Mengenai titik Anda menjalankan JUnits, Anda dapat menjalankan JUnits dari IDE seperti Eclipse bahkan jika Anda menggunakan Maven.
RLD
@RLD, satu-satunya cara saya tahu di Eclipse akan menjalankannya sebagai konfigurasi 'Maven' alih-alih Junit yang jauh lebih rumit dan hanya memiliki output teks daripada tampilan Junit normal. Dan saya tidak cukup mengikuti poin Anda tentang kode yang rapi dan ringkas dan harus menulis kode di banyak tempat. Bagi saya, memiliki data uji di pom yang kemudian digunakan dalam tes Junit lebih tidak jelas daripada memilikinya bersama-sama. Saya berada dalam situasi ini baru-baru ini dan akhirnya mengikuti pendekatan MathewFarwell, tidak perlu trik perpustakaan / pom dan semuanya bersama dalam tes yang sama.
Chirlo
1
Ini membuat variabel lingkungan hard-coded, dan mereka tidak dapat diubah dari satu permintaan System.getenv ke yang berikutnya. Benar?
Ian Stewart
12

Saya pikir cara terbersih untuk melakukan ini adalah dengan Mockito.spy (). Ini sedikit lebih ringan daripada membuat kelas terpisah untuk mengejek dan membagikannya.

Pindahkan variabel lingkungan Anda ke metode lain:

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

Sekarang di unit test Anda, lakukan ini:

@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
sumber
12

Saya rasa ini belum disebutkan, tetapi Anda juga bisa menggunakan Powermockito :

Diberikan:

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

Anda dapat melakukan hal berikut:

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
sumber
8
when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1")menyebabkan org.mockito.exceptions.misusing.MissingMethodInvocationException: when() requires an argument which has to be 'a method call on a mock'.kesalahan
Andremoniy
10

Pisahkan kode Java dari variabel Lingkungan yang menyediakan pembaca variabel yang lebih abstrak yang Anda sadari dengan EnvironmentVariableReader kode Anda untuk menguji bacaan dari.

Kemudian dalam pengujian Anda, Anda dapat memberikan implementasi yang berbeda dari pembaca variabel yang memberikan nilai tes Anda.

Injeksi ketergantungan dapat membantu dalam hal ini.

Andrea Colleoni
sumber
4

Semoga masalah ini teratasi. Saya hanya berpikir untuk memberi tahu solusi saya.

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
sumber
1
Anda lupa menyebutkan bahwa Anda menggunakan JMockit. :) Apapun, solusi ini juga bekerja sangat baik dengan JUnit 5
Ryan J. McDonough
2

Meskipun saya pikir jawaban ini adalah yang terbaik untuk proyek-proyek Maven, itu dapat dicapai melalui refleksi juga (diuji di Jawa 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);
    }
}
George Z.
sumber
Terima kasih untuk ini! Versi Java saya tampaknya tidak memiliki theCaseInsensitiveEnvironmentdan sebaliknya memiliki bidang theEnvironment, seperti berikut: `` `envMap = new HashMap <> (); Class <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Field theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); Lapangan theUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); `` `
Intenex
-2

Anda dapat menggunakan metode setup () untuk mendeklarasikan nilai yang berbeda dari env Anda. variabel dalam konstanta. Kemudian gunakan konstanta ini dalam metode pengujian yang digunakan untuk menguji skenario yang berbeda.

Cygnusx1
sumber
-2

Jika Anda ingin mengambil informasi tentang variabel lingkungan di Jawa, Anda dapat memanggil metode: System.getenv();. Sebagai properti, metode ini mengembalikan Peta yang berisi nama variabel sebagai kunci dan nilai variabel sebagai nilai peta. Berikut ini sebuah contoh:

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

Metode getEnv()ini juga dapat mengambil argumen. Misalnya :

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

Untuk pengujian, saya akan melakukan sesuatu seperti ini:

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
sumber
1
Poin utama adalah untuk tidak mengakses variabel env dari tes junit
Tanmoy Bhattacharjee
-2

Saya menggunakan System.getEnv () untuk mendapatkan peta dan saya simpan sebagai bidang, sehingga saya bisa mengejeknya:

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
sumber