有没有办法在Java中重新初始化静态类?

时间:2013-10-10 17:42:03

标签: java junit static

我正在尝试对引用另一个类的静态数据的类进行单元测试。我不能“不”使用这个静态类,但显然运行多个测试已经成为问题。所以我的问题是这个。在junit测试中有没有办法重新初始化静态类?那样一次测试不会受到先前测试的影响吗?

换句话说换句话说:

Foo.setBar("Hello");

// Somehow reinitialize Foo

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

不幸的是,我不能改变Foo,所以我不能使用它。

编辑看来我的例子有点过于简单了。在实际代码中,“Bar”由系统属性设置,并设置为内部静态变量。所以一旦它开始运行,我就无法改变它。

7 个答案:

答案 0 :(得分:5)

虽然有点脏,但我通过使用反射来解决这个问题。我没有重新运行静态初始化程序(这很好),而是采用了脆弱的方法并创建了一个实用程序,可以将字段设置回已知值。这是关于如何设置静态字段的示例。

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

答案 1 :(得分:4)

如果你使用PowerMock,你可以模拟静态方法 - 这是你应该做的。

答案 2 :(得分:2)

你可以使用PowerMock(和Mockito)或JMockit来模拟静态类,让它在每次测试中做任何你想做的事情。

答案 3 :(得分:2)

三个建议,

  1. @Before设置静态方法将其设置为某个已知值。

  2. 使用ReflectionTestUtils通过反射设置值。

  3. 更新代码以使实例包装器类包装对实例方法/类中的静态方法的调用。模拟包装并注入您正在测试的类中。

答案 4 :(得分: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;
      }
  } 
}

所以每个人都不能运行:

FooFactory.init();// on start

....

FooFactory.destroy();// on finish

答案 5 :(得分:0)

从技术上讲,可以将类(以及测试所需的其他类)加载到自己的类加载器中 - 您必须确保从根类加载器无法访问该类,但是,这需要相当多的黑客才能做到这一点,我怀疑在正常的单元测试中是否可行。然后你可以删除类加载器并重新初始化它以进行下一个测试 - 每个类加载器都有自己的静态变量用于它所加载的所有类。

或者,做一点重量级并为每个测试分叉一个新的JVM。我以前做过这个,它可以工作(特别有用于进行更复杂的集成测试,这些测试会破坏系统属性,否则不能轻易模拟),但它可能不是你想要的每个构建运行的单元测试。

当然,这些技术也可以组合在一起(如果你没有从类类加载器中获取类) - 在类路径上用一个最小的“驱动程序”分叉一个新的JVM,用“the”初始化一个新的类加载器正常的“每个测试运行的类路径。”

答案 6 :(得分:0)

这是一个小例子,其中重新加载使用静态初始化程序的实用程序类来测试该实用程序的初始化。 该实用程序使用系统属性初始化静态最终值。通常,此值不能在运行时更改。 所以jUnit-test重新加载类以重新运行静态初始化器...

实用程序:

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

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