JUnit kullanarak ortam değişkenlerine bağlı kod nasıl test edilir?


140

Bir ortam değişkeni kullanan bir Java kodu parçası var ve kod davranışı bu değişkenin değerine bağlıdır. Bu kodu ortam değişkeninin farklı değerleri ile test etmek istiyorum. Bunu JUnit'te nasıl yapabilirim?

Genel olarak Java'da ortam değişkenlerini ayarlamanın bazı yollarını gördüm , ancak özellikle testlerin birbiriyle etkileşmemesi gerektiğini düşünerek, birim test yönüyle daha fazla ilgileniyorum.


Bu test için olduğundan, Sistem Kuralları birim test kuralı şu anda en iyi yanıt olabilir.
17'de Atifm

3
Sadece JUnit 5 kullanırken aynı soru ile ilgilenenler için: stackoverflow.com/questions/46846503/…
Felipe Martins Melo

Yanıtlar:


199

Sistem Kuralları kitaplığı , ortam değişkenlerini ayarlamak için bir JUnit Kuralı sağlar.

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

Feragatname: Sistem Kurallarının yazarıyım.


1
Bunu @ClassRule olarak kullanıyorum, kullandıktan sonra sıfırlamam veya temizlemem gerekiyor mu, evet ise nasıl?
Mritunjay

Gerek yok. Sınıftaki tüm testler yapıldıktan sonra orijinal ortam değişkenleri kural tarafından otomatik olarak sıfırlanır.
Stefan Birkner

Bu yaklaşım yalnızca JUnit 4 veya üstü sürümlerde çalışır. JUnit 3 veya daha düşük sürümler için veya JUnit 4 ve JUnit 3'ü karıştırırsanız önerilmez
RLD

2
import org.junit.contrib.java.lang.system.EnvironmentVariables;com.github.stefanbirkner:system-rulesProjenize bağımlılığı eklemeniz gerekecek . MavenCentral'da mevcuttur.
Jean Bob

2
Bağımlılığı eklemek için talimatlar: stefanbirkner.github.io/system-rules/download.html
Guilherme Garnier

77

Genel çözüm, bu çevresel değişkene erişimi yöneten ve daha sonra test sınıfınızda alay edebileceğiniz bir sınıf oluşturmaktır.

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

Test edilen sınıf daha sonra doğrudan System.getenv () öğesinden değil, Environment sınıfını kullanarak ortam değişkenini alır.


1
Bu sorunun eski olduğunu biliyorum, ama bunun doğru cevap olduğunu söylemek istedim. Kabul edilen cevap, sisteme gizli bir bağımlılıkla zayıf tasarımı teşvik ederken, bu cevap Sistemi enjekte edilmesi gereken başka bir bağımlılık olarak ele alan uygun bir tasarım teşvik eder.
Andrew

30

Çevre Değişkeni'ne bağlı Test Kutusu yazmak zorunda kaldığım buna benzer bir durumda aşağıdakileri denedim:

  1. Stefan Birkner'ın önerdiği gibi Sistem Kuralları'na gittim . Kullanımı basitti. Ama er geç, davranışı düzensiz buldum. Bir koşuda çalışır, bir sonraki koşuda başarısız olur. Sistem Kurallarının JUnit 4 veya daha yüksek bir sürümle iyi çalıştığını araştırdım ve buldum. Ama benim durumlarımda, JUnit 3'e bağlı bazı kavanozlar kullanıyordum . Bu yüzden Sistem Kurallarını atladım . Daha fazla bilgiyi burada bulabilirsiniz @ Kural ek açıklaması JUnit'te TestSuite kullanılırken çalışmaz .
  2. Sonra Java tarafından sağlanan Process Builder sınıfı aracılığıyla Ortam Değişkeni oluşturmaya çalıştım . Burada Java Kodu aracılığıyla bir ortam değişkeni oluşturabiliriz, ancak yapmadığım işlem veya program adını bilmeniz gerekir . Ayrıca, ana süreç için değil, alt süreç için ortam değişkeni oluşturur.

Yukarıdaki iki yaklaşımı kullanarak bir gün boşa harcadım, ama boşuna. Sonra Maven kurtarmaya geldi. Maven tabanlı proje için Birim Testi yapmanın en iyi yolu olduğunu düşündüğüm Maven POM dosyası aracılığıyla Ortam Değişkenleri veya Sistem Özellikleri'ni ayarlayabiliriz . POM dosyasında yaptığım giriş aşağıdadır .

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

Bu değişiklikten sonra tekrar Test Durumlarını çalıştırdım ve birden beklediğim gibi çalıştı. Okuyucunun bilgisi için Maven 3.x'te bu yaklaşımı araştırdım , bu yüzden Maven 2.x hakkında hiçbir fikrim yok .


2
Bu çözüm en iyisidir ve kabul edilen çözüm olmalıdır, çünkü bir lib gibi ek bir şeye ihtiyacınız olmayacaktır. Maven tek başına yeterince kullanışlıdır. Teşekkürler @RLD
Semo

@Bu nedenle, bir lib kullanmaktan çok daha büyük bir gereklilik olan maven gerektirir. Junit Testini pom'a bağlar ve testin her zaman doğrudan IDE'de normal şekilde çalıştırmak yerine mvn'den yapılması gerekir.
Chirlo

@Chirlo, programınızın neyle bağlantı kurmasını istediğinize bağlıdır. Maven'i kullanarak tek bir yerde yapılandırabilir ve düzgün ve özlü kod yazabilirsiniz. Kitaplık kullanıyorsanız, birden çok yere kod yazmanız gerekir. JUnits'i çalıştırmak için, Maven kullansanız bile JUnits'i Eclipse gibi IDE'den çalıştırabilirsiniz.
RLD

@RLD, Eclipse içinde bildiğim tek yol, çok daha hantal ve normal Junit görünümü yerine sadece metin çıktısı olan bir Junit yerine 'Maven' çalıştırma yapılandırması olarak çalıştırmak olurdu. Ve düzgün ve özlü kod ve çoklu yerlerde kod yazmak zorunda noktanızı tam olarak takip etmiyorum. Benim için, pom'da daha sonra bir Junit testinde kullanılan test verilerinin bulunması, bunları bir araya getirmekten daha belirsizdir. Son zamanlarda bu durumdaydım ve MathewFarwell'in yaklaşımını takip ettim, kütüphanelere / pom hilelerine gerek yok ve her şey aynı testte bir arada.
Chirlo

1
Bu, ortam değişkenlerini kodlanmış hale getirir ve System.getenv'in bir çağrısından diğerine değiştirilemez. Doğru?
Ian Stewart

12

Bunu yapmanın en temiz yolunun Mockito.spy () ile olduğunu düşünüyorum. Alay etmek ve etrafta dolaşmak için ayrı bir sınıf oluşturmaktan biraz daha hafif.

Ortam değişkeninizi başka bir yönteme taşıyın:

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

Şimdi birim testinizde şunları yapın:

@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

Bunun henüz bahsedilmediğini sanmıyorum, ancak Powermockito'yu da kullanabilirsiniz :

Verilen:

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

Aşağıdakileri yapabilirsiniz:

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'.hataya neden olur
Andremoniy

10

Java kodunu Ortam değişkeninden ayırın ve okumaları test etmek için kodunuzu bir EnvironmentVariableReader ile gerçekleştirdiğiniz daha soyut bir değişken okuyucu sağlayın.

Daha sonra testinizde, değişken değerlerinin test değerlerinizi sağlayan farklı bir uygulamasını verebilirsiniz.

Bağımlılık enjeksiyonu bu konuda yardımcı olabilir.



4

Umarım sorun çözülür. Sadece çözümümü anlatmayı düşündüm.

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 kullandığınızı belirtmeyi unuttunuz. :) Ne olursa olsun, bu çözüm de JUnit 5 ile harika çalışıyor
Ryan J. McDonough

2

Bu cevap Maven projeleri için en iyisi olduğunu düşünmeme rağmen , yansıma yoluyla da elde edilebilir ( Java 8'de test edilmiştir ):

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

Bunun için teşekkürler! Java sürümümde aşağıdaki gibi görünmüyor theCaseInsensitiveEnvironmentve bunun yerine bir alan var theEnvironment: `` envMap = new HashMap <> (); Sınıf <?> Clazz = Class.forName ("java.lang.ProcessEnvironment"); Field theEnvironmentField = clazz.getDeclaredField ("theEnvironment"); AlanUnmodifiableEnvironmentField = clazz.getDeclaredField ("theUnmodifiableEnvironment"); removeStaticFinalAndSetValue (theEnvironmentField, envMap); removeStaticFinalAndSetValue (theUnmodifiableEnvironmentField, envMap); ``
Intenex

-2

Env'nizin farklı değerlerini bildirmek için setup () yöntemini kullanabilirsiniz. değişmezler. Ardından, farklı senaryoları test etmek için kullanılan test yöntemlerinde bu sabitleri kullanın.


-2

Eğer Java ortam değişkeni hakkında bilgi almak isterseniz, yöntemini çağırabilirsiniz: System.getenv();. Özellikler olarak, bu yöntem anahtar olarak değişken adlarını ve harita değerleri olarak değişken değerlerini içeren bir Harita döndürür. İşte bir örnek :

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

Yöntem getEnv()ayrıca bir argüman alabilir. Örneğin :

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

Test için böyle bir şey yapardım:

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
Ana nokta, env değişkenlerine junit testinden
erişmemek

-2

Haritayı almak için System.getEnv () kullanın ve bir alan olarak tutmak, böylece alay edebilirsiniz:

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"));
         }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.