繁体   English   中英

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

[英]Using reflection to change static final File.separatorChar for unit testing?

具体来说,我正在尝试为一个方法创建一个单元测试,该方法需要使用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

思考?

Field.set的文档:

如果基础字段是final,则该方法抛出IllegalAccessException除非此字段的setAccessible(true)成功, 并且此字段是非静态的

所以起初看起来你运气不好,因为File.separatorCharstatic 出人意料的是, 一种方法来解决这个问题:简单地使static场不再是final通过反射。

从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

尝试调用不在类File实例上的文件实例

例如

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

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

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

我意识到这并没有直接回答你的问题,但Apache Commons FileNameUtils会做跨平台的文件名构建,并且可以节省你编写自己的类来做到这一点。

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

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

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

如果基础字段是最后的方法,这样我们需要使用setAccessible(真) 抛出一个IllegalAccessException,NoSuchFieldException需要时使用field.set()方法来添加

@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 ”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM