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

时间:2010-03-18 23:20:43

标签: java unit-testing reflection file-io

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

任何人都知道发生了什么事?

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

思想?

7 个答案:

答案 0 :(得分:64)

来自Field.set的文档:

  

如果基础字段是最终字段,则该方法会抛出IllegalAccessException,除非此字段成功setAccessible(true)且此字段为非静态

所以起初看起来你运气不好,因为File.separatorCharstatic。令人惊讶的是, 是一种解决此问题的方法:只需通过反射使static字段不再final

我改编了这个解决方案from 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"

重要更新:上述解决方案在所有情况下均无效。如果在重置之前可以访问该字段并通​​过Reflection读取,则会引发IllegalAccessException。它失败是因为Reflection API创建了内部FieldAccessor对象,这些对象被缓存并重用(请参阅java.lang.reflect.Field#acquireFieldAccessor(boolean)实现)。 示例测试代码失败:

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

答案 1 :(得分:2)

尝试调用不在类File

实例上的文件实例

E.g。

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

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

答案 2 :(得分:2)

构建文件时只需使用/到处。我已经这样做了13年,从来没有遇到过问题。没有什么可以测试的。

答案 3 :(得分:1)

我意识到这并没有直接回答你的问题,但是Apache Commons FileNameUtils会做跨平台的文件名构建,并且可以省去你自己写的课程。

答案 4 :(得分:0)

不要使用File.separatorChar声明你的服务类,而是让它称之为PathBuilder。这个类将有一个concatPaths()方法,它将连接这两个参数(使用操作系统的分隔符char)。美妙之处在于您正在编写本课程,因此您可以在进行单元测试时随意调整它。

答案 5 :(得分:0)

您可以获取java.io.File的源代码,并修改它以使separatorChar和separator不是final,并添加一个setSeparatorChar方法来更新其中的两个,然后在编译类路径中包含已编译的类。

答案 6 :(得分:0)

这里我要设置" android.os.Build.VERSION.RELEASE"的值,其中VERSION是类名,RELEASE是最终的静态字符串值。

  

如果基础字段是最终字段,则方法抛出    IllegalAccessException 以便我们需要使用setAccessible(true),   使用 field.set()方法

时,需要添加 NoSuchFieldException
@RunWith(PowerMockRunner.class)
@PrepareForTest({Build.VERSION.class})
public class RuntimePermissionUtilsTest {
@Test
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException {
    Field field = Build.VERSION.class.getField("RELEASE");
    field.setAccessible(true);
    field.set(null,"Marshmallow");
 }
}

现在String RELEASE 的值将返回" Marshmallow "。