有没有办法在Java中重新初始化静态类?
我试图对引用另一个类的静态数据的类进行单元测试。我不能“不”使用这个静态类,但显然运行多个测试已经成了问题。所以我的问题是。junit测试中有没有办法重新初始化静态类?这样一来,一次测试就不会受到前一次测试的影响了 换句话说,就是这样做的方式:有没有办法在Java中重新初始化静态类?,java,junit,static,Java,Junit,Static,我试图对引用另一个类的静态数据的类进行单元测试。我不能“不”使用这个静态类,但显然运行多个测试已经成了问题。所以我的问题是。junit测试中有没有办法重新初始化静态类?这样一来,一次测试就不会受到前一次测试的影响了 换句话说,就是这样做的方式: Foo.setBar("Hello"); // Somehow reinitialize Foo String bar = Foo.getBar(); // Gets default value of bar rather than "Hello"
Foo.setBar("Hello");
// Somehow reinitialize Foo
String bar = Foo.getBar(); // Gets default value of bar rather than "Hello"
不幸的是,我无法更改Foo,所以我一直在使用它
编辑看来我的例子太简单了。在实数代码中,“Bar”由系统属性设置,并设置为内部静态变量。因此,一旦它开始运行,我就无法更改它。如果您使用PowerMock,您可以模拟静态方法——这是您应该做的。您可以使用PowerMock(带Mockito)或JMockit模拟静态类,让它在每个测试中执行您想要的任何操作 三点建议
@调用该方法
ReflectionTestUtils
通过反射设置值我将使用
Factory
模式和init
和destroy
静态方法来处理所有实例
比如:
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
从技术上讲,可以将类(以及测试所需的一些其他类)加载到它自己的类加载器中——但是,您必须确保从根类加载器无法访问该类,因此这需要相当多的黑客操作,我怀疑这在正常的单元测试中是可能的。然后,您可以删除类加载器并为下一个测试重新初始化它-每个类加载器都有自己的静态变量用于它所加载的所有类 或者,做得更重一点,并为每个测试提供一个新的JVM。我以前做过这项工作,它是有效的(尤其适用于做更复杂的集成测试,这些测试会弄乱系统属性,否则很难进行模拟),但这可能不是您希望为每个构建运行的单元测试所需要的
当然,这些技术也可以结合使用(如果你没有从根类加载器中取出类的话)——在类路径上创建一个带有最小“驱动程序”的新JVM,它用“正常”类路径初始化一个新的类加载器,以运行每个测试。虽然有点脏,但我通过使用反射解决了这个问题。我没有重新运行静态初始值设定项(这很好),而是采用了脆弱的方法,创建了一个实用程序,将字段设置回已知值。下面是一个关于如何设置静态字段的示例
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);
下面是一个小示例,其中使用静态初始值设定项的实用程序类被重新加载以测试该实用程序的初始化。 该实用程序使用系统属性初始化静态最终值。通常在运行时无法更改此值。 因此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);
}
}
}
导入静态org.junit.Assert.assertEquals;
导入java.io.ByteArrayOutputStream;
导入java.io.IOException;
导入java.io.InputStream;
导入java.lang.reflect.InvocationTargetException;
导入java.lang.reflect.Method;
导入org.junit.Test;
公共类公用测试{
私有类MyClassLoader扩展了ClassLoader{
公共类load()引发IOException{
InputStream=MyClassLoader.class.getResourceAsStream(“/Util.class”);
ByteArrayOutputStream bas=新的ByteArrayOutputStream();
int b=-1;
而((b=is.read())>-1){
编写(b);
}
返回super.defineClass(“Util”,baos.toByteArray(),0,baos.size());
}
}
@试验
public void testGetValue(){
assertEquals(“默认”,getValue());
系统设置属性(“价值”、“abc”);
资产质量(“abc”,getValue());
}
私有字符串getValue(){
试一试{
MyClassLoader MyClassLoader=新的MyClassLoader();
类clazz=myClassLoader.load();
方法Method=clazz.getMethod(“getValue”);
对象结果=method.invoke(clazz);
返回(字符串)结果;
}捕获(IOException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e){
抛出新的IllegalStateException(“在'getValue'处出错:”+e.getLocalizedMessage(),e);
}
}
}
不清楚你在问什么。您是在寻找JUnit功能在特定时间(何时?)运行代码,还是在询问是否可以在初始化后修改外部Foo
类?当然,您可以修改Foo使其可变,但显然禁止您这样做。唯一的其他选择是使用私有类装入器和反射来允许重新装入类。但是Foo.getBar()
调用必须重新处理。可能重复的Foo
是否有任何设置程序可以使其返回到初始化状态?(1)只有在没有其他测试修改该值的情况下才起作用-即,该值很脆弱。为什么不运行静态初始值设定程序?我在办公室工作时发现了这一点,必须返回+1。可悲的是,当测试作为一个整体运行时(gradlew测试),jacoco或intellij覆盖率运行者没有考虑这些JUnit。但是,如果覆盖率是针对单个类(UtilTest)运行的,则会考虑它。不确定原因:(