【问题标题】:Is there a way to reinitialize a static class in Java?有没有办法在 Java 中重新初始化静态类?
【发布时间】:2013-10-10 17:42:02
【问题描述】:

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

换句话说,这样做的某种方式:

Foo.setBar("Hello");

// Somehow reinitialize Foo

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

不幸的是,我无法更改 Foo,所以我无法使用它。

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

【问题讨论】:

  • 不清楚你在问什么。您是在寻找在特定时间(何时?)运行代码的 JUnit 功能,还是在询问是否可以在初始化后修改外部 Foo 类?
  • 当然,您可以修改 Foo 使其可变,但显然禁止这样做。唯一的其他选择是使用私有类加载器和反射来允许重新加载类。但是Foo.getBar() 的调用必须重新进行。
  • Foo 是否有任何设置器可以通过努力将其恢复到初始化状态?

标签: java junit static


【解决方案1】:

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

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

【讨论】:

  • 你为什么运行静态初始化程序?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    您可以使用 PowerMock(使用 Mockito)或 JMockit 来模拟静态类,让它在每个测试中执行您想要的任何操作。

    【讨论】:

      【解决方案4】:

      三个建议,

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

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

      3. 更新您的代码,使其具有一个实例包装类,该类将对静态方法的调用包装在实例方法/类中。模拟包装器并注入到您的测试类中。

      【讨论】:

      • (1) 仅在没有其他测试修改该值时才有效 - 即它很脆弱。
      【解决方案5】:

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

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

      【讨论】:

      • 我在办公室发现了这个,必须回来+1。可悲的是,当测试作为一个整体运行时(gradlew 测试),jacoco 或 intellij 覆盖运行者没有考虑这些 junit。但是,如果针对单个类 (UtilTest) 运行覆盖率,则会考虑它。不知道为什么:(
      【解决方案6】:

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

      或者,做一些更重量级的工作,并为每个测试派生一个新的 JVM。我以前做过这个,它可以工作(对于做更复杂的与系统属性混淆的集成测试特别有用,否则不容易被模拟),但对于为每个构建运行的单元测试来说,它可能不是你想要的。 .

      当然,这些技术也可以结合使用(如果你没有从根类加载器中获取类)——在类路径上使用最小的“驱动程序”派生一个新的 JVM,它使用“初始化一个新的类加载器”运行每个测试的 normal" 类路径。

      【讨论】:

        【解决方案7】:

        我会将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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-04-15
          • 2012-05-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-07
          相关资源
          最近更新 更多