11

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

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

Foo.setBar("Hello");

// Somehow reinitialize Foo

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

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

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

4

7 回答 7

6

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

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);
于 2014-01-20T15:03:01.577 回答
4

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

于 2013-10-10T17:46:19.317 回答
2

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

于 2013-10-10T17:47:42.890 回答
2

三个建议,

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

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

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

于 2013-10-10T17:48:35.637 回答
1

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

实用程序:

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);
        }
    }
}
于 2014-11-19T16:10:10.493 回答
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
于 2013-10-10T17:48:48.380 回答
0

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

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

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

于 2013-10-10T17:53:12.130 回答