2013-10-10 145 views
10

我想單元測試一個引用另一個類的靜態數據的類。我不能「不」使用這個靜態類,但顯然運行多個測試已成爲問題。所以我的問題是這樣的。在junit測試中有沒有辦法重新初始化靜態類?那樣一個測試不受前面的測試影響?有沒有辦法在Java中重新初始化靜態類?

因此,換句話說,一些這樣的方式:

Foo.setBar("Hello"); 

// Somehow reinitialize Foo 

String bar = Foo.getBar(); // Gets default value of bar rather than "Hello" 

不幸的是,我不能改變富,所以我堅持使用它。

編輯看來我讓我的例子太簡單了。在實際代碼中,「Bar」由系統屬性設置,並被設置爲內部靜態變量。所以一旦開始運行,我無法改變它。

+0

目前還不清楚你在問什麼。你是否正在尋找一個JUnit特性來在特定的時間運行代碼(何時?),或者你是否在初始化時是否可以修改外部'Foo'類? – chrylis

+0

當然,您可以修改Foo以使其可變,但您明顯禁止這樣做。唯一的其他選擇是使用私有類加載器和反射來允許類重新加載。但是'Foo.getBar()'調用將不得不被重寫。 –

+1

[Java:如何「重新啓動」一個靜態類的可能的重複?](http://stackoverflow.com/questions/4631565/java-how-to-restart-a-static-class) –

回答

4

雖然它有點髒,但我通過使用反射來解決這個問題。我沒有重新運行靜態初始化程序(這很好),而是採用了脆弱的方法,並創建了一個實用程序,將字段設置回已知值。以下是我將如何設置靜態字段的示例。

final Field field = clazz.getDeclaredField(fieldName); 
field.setAccessible(true); 
final Field modifiersField = Field.class.getDeclaredField("modifiers"); 
modifiersField.setAccessible(true); 
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 

field.set(null, value); 
5

如果你使用PowerMock,你可以模擬靜態方法 - 這是你應該做的。

2

您可以使用PowerMock(帶Mockito)或JMockit來模擬靜態類,使其在每個測試中都可以執行任何您想要的操作。

2

三點建議,

  1. 呼叫從@Before靜態方法將它設置爲某些已知值。使用ReflectionTestUtils通過反射設置值。

  2. 更新您的代碼以將實例包裝類包裝爲實例方法/類中靜態方法的調用。模擬包裝並注入你的課堂。

+0

(1)僅在沒有其他測試修改該值時才起作用 - 即它是脆弱的。 – ash

0

我會用Factory圖案initdestroy應採取所有實例保健靜態方法。

喜歡的東西:

public class FooFactory { 

    private static Foo mFoo = null; 

    public static Foo init(){ 

     if(mFoo == null){ 
      mFoo = new Foo(); 
     } 
     return mFoo; 
    } 

    public static void destroy(){ 
     if(mFoo != null){ 
      mFoo = null; 
     } 
    } 
} 

所以每UNITEST足夠運行:

​​
0

從技術上講,可以(與被試驗所需的一些其他類一起)加載類放到它自己的類加載器中 - 但是你必須確保類不能從根類加載器訪問,所以這需要相當多的黑客來做這件事,而且我懷疑它在正常的單元測試中是可能的。然後,您可以刪除類加載器並重新初始化它以進行下一個測試 - 每個類加載器都有它自己的所有類加載的靜態變量。

另外,做一個更重量級,併爲每個測試分叉一個新的JVM。我之前完成了這個工作,它的工作原理(特別適用於執行更復雜的集成測試,這些測試會混淆系統屬性,否則不會輕易被模擬),但它可能不是您希望爲每個構建運行的單元測試。

當然,這些技術也可以結合使用(如果您沒有從根類加載器中獲得類) - 在類路徑中分配一個帶有最小「驅動程序」的新JVM,該類將初始化一個新的類加載器每個測試運行的「正常」類路徑。

0

這裏是一個小例子,其中使用靜態初始化一個工具類重新加載到該實用程序的測試初始化​​。 該實用程序使用系統屬性來初始化靜態最終值。通常這個值不能在運行時改變。 所以了JUnit測試重新加載類重新運行靜態初始化...

效用:

public class Util { 
    private static final String VALUE; 

    static { 
     String value = System.getProperty("value"); 

     if (value != null) { 
      VALUE = value; 
     } else { 
      VALUE = "default"; 
     } 
    } 

    public static String getValue() { 
     return VALUE; 
    } 
} 

了JUnit測試:

import static org.junit.Assert.assertEquals; 

import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 

import org.junit.Test; 

public class UtilTest { 

    private class MyClassLoader extends ClassLoader { 

     public Class<?> load() throws IOException { 
      InputStream is = MyClassLoader.class.getResourceAsStream("/Util.class"); 

      ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
      int b = -1; 

      while ((b = is.read()) > -1) { 
       baos.write(b); 
      } 

      return super.defineClass("Util", baos.toByteArray(), 0, baos.size()); 
     } 
    } 

    @Test 
    public void testGetValue() { 
     assertEquals("default", getValue()); 
     System.setProperty("value", "abc"); 
     assertEquals("abc", getValue()); 
    } 

    private String getValue() { 
     try { 
      MyClassLoader myClassLoader = new MyClassLoader(); 
      Class<?> clazz = myClassLoader.load(); 
      Method method = clazz.getMethod("getValue"); 
      Object result = method.invoke(clazz); 
      return (String) result; 
     } catch (IOException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { 
      throw new IllegalStateException("Error at 'getValue': " + e.getLocalizedMessage(), e); 
     } 
    } 
} 
相關問題