簡體   English   中英

在單元測試期間重置類靜態變量

[英]Reset class static variable during unit test

我正在嘗試為遺留代碼編寫單元測試。 我正在測試的類有幾個靜態變量。 我的測試用例類有一些@Test方法。 因此,他們都擁有相同的狀態。

有沒有辦法在測試之間重置所有靜態變量?

我提出的一個解決方案是明確重置每個字段,例如:

field(MyUnit.class, "staticString").set(null, null);
((Map) field(MyUnit.class, "staticFinalHashMap").get(null)).clear();

如您所見,每個變量都需要自定義重新初始化。 這種方法不容易擴展,遺留代碼庫中有很多這樣的類。 有沒有辦法一次性重置所有內容? 也許每次重新上課?

作為一個可能的好解決方案,我認為是使用類似powermock的東西,並為每個測試創建一個單獨的類加載器。 但我看不到簡單的方法。

好吧,我想我弄明白了。 這很簡單。

可以將@PrepareForTest powermock的注釋移動到方法級別。 在這種情況下,powermock為每個方法創建類加載器。 所以我需要它。

假設我正在測試涉及此類的一些代碼:

import java.math.BigInteger;
import java.util.HashSet;

public class MyClass {
  static int someStaticField = 5;
  static BigInteger anotherStaticField = BigInteger.ONE;
  static HashSet<Integer> mutableStaticField = new HashSet<Integer>();
}

您可以使用Java的反射功能以編程方式重置所有靜態字段。 在開始測試之前,您需要存儲所有初始值,然后在每次測試運行之前需要重置這些值。 JUnit有@BeforeClass@Before注釋,可以很好地解決這個問題。 這是一個簡單的例子:

import static org.junit.Assert.*;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Map;
import java.util.HashMap;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class MyTest extends Object {

  static Class<?> staticClass = MyClass.class;
  static Map<Field,Object> defaultFieldVals = new HashMap<Field,Object>();

  static Object tryClone(Object v) throws Exception {
    if (v instanceof Cloneable) {
      return v.getClass().getMethod("clone").invoke(v);
    }
    return v;
  }

  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    Field[] allFields = staticClass.getDeclaredFields();
    try {
      for (Field field : allFields) {
          if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
              Object value = tryClone(field.get(null));
              defaultFieldVals.put(field, value);
          }
      }
    }
    catch (IllegalAccessException e) {
      System.err.println(e);
      System.exit(1);
    }
  }

  @AfterClass
  public static void tearDownAfterClass() {
    defaultFieldVals = null;
  }

  @Before
  public void setUp() throws Exception {
    // Reset all static fields
    for (Map.Entry<Field, Object> entry : defaultFieldVals.entrySet()) {
      Field field = entry.getKey();
      Object value = entry.getValue();
      Class<?> type = field.getType();
      // Primitive types
      if (type == Integer.TYPE) {
        field.setInt(null, (Integer) value);
      }
      // ... all other primitive types need to be handled similarly
      // All object types
      else {
        field.set(null, tryClone(value));
      }
    }
  }

  private void testBody() {
    assertTrue(MyClass.someStaticField == 5);
    assertTrue(MyClass.anotherStaticField == BigInteger.ONE);
    assertTrue(MyClass.mutableStaticField.isEmpty());
    MyClass.someStaticField++;
    MyClass.anotherStaticField = BigInteger.TEN;
    MyClass.mutableStaticField.add(1);
    assertTrue(MyClass.someStaticField == 6);
    assertTrue(MyClass.anotherStaticField.equals(BigInteger.TEN));
    assertTrue(MyClass.mutableStaticField.contains(1));
  }

  @Test
  public void test1() {
    testBody();
  }

  @Test
  public void test2() {
    testBody();
  }

}

正如我在setUp()的注釋中所提到的,你需要使用類似的代碼處理其余的基本類型來處理int 所有包裝類都有一個TYPE字段(例如Double.TYPECharacter.TYPE ),您可以像Integer.TYPE一樣檢查它。 如果字段的類型不是基本類型之一(包括基本數組),那么它是一個Object ,可以作為通用Object處理。

可能需要調整代碼以處理finalprivateprotected字段,但您應該能夠從文檔中了解如何執行此操作。

祝你的遺產代碼好運!

編輯:

我忘了提一下,如果存儲在其中一個靜態字段中的初始值發生了變異,那么只需緩存它並恢復它就不會有效,因為它只會重新分配變異對象。 我還假設您將能夠擴展此代碼以使用靜態類數組而不是單個類。

編輯:

我已經添加了一個Cloneable對象的檢查來處理你的例子中的HashMap類的情況。 顯然它並不完美,但希望這將涵蓋你將遇到的大多數情況。 希望沒有足夠的邊緣情況,手動重置它們不會太大(即將重置代碼添加到setUp()方法)。

這是我的兩分錢

1.將靜態引用提取到getter / setter中

當您能夠創建它的子類時,這可以工作。

public class LegacyCode {
  private static Map<String, Object> something = new HashMap<String, Object>();

  public void doSomethingWithMap() {

    Object a = something.get("Object")
    ...
    // do something with a
    ...
    something.put("Object", a);
  }
}

變成

public class LegacyCode {
  private static Map<String, Object> something = new HashMap<String, Object>();

  public void doSomethingWithMap() {

    Object a = getFromMap("Object");
    ...
    // do something with a
    ...
    setMap("Object", a);
  }

  protected Object getFromMap(String key) {
    return something.get(key);
  }

  protected void setMap(String key, Object value) {
    seomthing.put(key, value);
  }
}

然后你可以通過子類擺脫依賴它。

public class TestableLegacyCode extends LegacyCode {
  private Map<String, Object> map = new HashMap<String, Object>();

  protected Object getFromMap(String key) {
    return map.get(key);
  }

  protected void setMap(String key, Object value) {
    map.put(key, value);
  }
}

2.介紹靜態setter

這一點應該非常明顯。

public class LegacyCode {
  private static Map<String, Object> something = new HashMap<String, Object>();

  public static setSomethingForTesting(Map<String, Object> somethingForTest) {
    something = somethingForTest;
  }

  ....
}

兩種方式都不漂亮,但是一旦我們進行測試,我們總能回來。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM