使用反射来更改静态最终 File.separatorChar 进行单元测试?

2022-09-01 13:43:49

具体来说,我正在尝试为一个方法创建一个单元测试,该方法需要用于在Windows和unix上构建路径。代码必须在两个平台上运行,但是当我尝试更改此静态最终字段时,JUnit会出现错误。File.separatorChar

有人知道发生了什么吗?

Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');

当我这样做时,我会得到

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character

思潮?


答案 1

Field.set 的文档:

如果基础字段是 final,则该方法将为此字段引发一个 if,并且此字段是非静态的IllegalAccessExceptionsetAccessible(true)

所以一开始看来你运气不好,因为是.令人惊讶的是,一种方法可以解决这个问题:只需通过反射使该领域不再存在。File.separatorCharstaticstaticfinal

我从 javaspecialist.eu 中采用了这个解决方案:

static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
}

我已经测试了它,它的工作原理:

setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"

使用这种技术时要格外小心。撇开破坏性后果不谈,以下情况实际上有效:

setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"

重要更新:上述解决方案并非在所有情况下都有效。如果该字段在重置之前可访问并通读反射,则会抛出 。它之所以失败,是因为反射 API 创建了缓存和重用的内部对象(请参阅 java.lang.reflect.Field#acquireFieldAccessor(布尔)实现)。失败的示例测试代码:IllegalAccessExceptionFieldAccessor

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException

答案 2

尝试在 file 的实例上调用,而不是在类 File 的实例上调用

例如:

File file = ...;    
field.setChar(file,'/');

你也可以尝试 http://code.google.com/p/jmockit/ 并模拟静态方法FileSystem.getFileSystem()。(不知道你是否可以模拟静态变量,通常那些黑客不应该是必要的 - >编写oo代码并使用“仅”mockito)


推荐