Java 使用反射更改静态最终 File.separatorChar 进行单元测试?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2474017/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using reflection to change static final File.separatorChar for unit testing?
提问by Stefan Kendall
Specifically, I'm trying to create a unit test for a method which requires uses File.separatorChar
to build paths on windows and unix. The code must run on both platforms, and yet I get errors with JUnit when I attempt to change this static final field.
具体来说,我正在尝试为需要用于File.separatorChar
在 Windows 和 unix 上构建路径的方法创建单元测试。代码必须在两个平台上运行,但是当我尝试更改此静态最终字段时,JUnit 出现错误。
Anyone have any idea what's going on?
任何人都知道发生了什么?
Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');
When I do this, I get
当我这样做时,我得到
IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character
Thoughts?
想法?
采纳答案by polygenelubricants
From the documentation for Field.set
:
从文档中Field.set
:
If the underlying field is final, the method throws an
IllegalAccessException
unlesssetAccessible(true)
has succeeded for this field and this field is non-static.
如果基础字段是 final ,则该方法会抛出一个
IllegalAccessException
直到setAccessible(true)
该字段成功并且该字段是非静态的。
So at first it seems that you are out of luck, since File.separatorChar
is static
. Surprisingly, there isa way to get around this: simply make the static
field no longer final
through reflection.
因此,起初似乎您不走运,因为File.separatorChar
是static
。令人惊讶的是一种方法来解决这个问题:简单地让static
现场不再final
通过反射。
I adapted this solution from javaspecialist.eu:
我从 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);
}
I've tested it and it works:
我已经测试过它并且它有效:
setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"
Do exercise extreme caution with this technique. Devastating consequences aside, the following actually works:
使用这种技术要格外小心。撇开毁灭性的后果不谈,以下实际上有效:
setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"
Important update: the above solution does notwork in all cases. If the field is made accessible and read through Reflection before it gets reset, an IllegalAccessException
is thrown. It fails because the Reflection API creates internal FieldAccessor
objects which are cached and reused (see the java.lang.reflect.Field#acquireFieldAccessor(boolean) implementation).
Example test code which fails:
重要更新:上述解决方案不适用于所有情况。如果该字段在重置之前可访问并通过反射读取,IllegalAccessException
则会抛出an 。它失败是因为反射 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
回答by Itay Maman
Instead of using File.separatorChar declare your service Class, let's call it PathBuilder or something. This class will have a concatPaths() method which will concatenate the two parameters (using the OS's separator char). The beauty is that you are writing this class so you can tweak it anyway you want when you unit test it.
与其使用 File.separatorChar 声明您的服务类,不如称其为 PathBuilder 或其他名称。这个类将有一个 concatPaths() 方法,它将连接两个参数(使用操作系统的分隔符)。美妙之处在于您正在编写这个类,因此您可以在单元测试时随意调整它。
回答by Karussell
Try invoking on an instance of file not on an instance of class File
尝试调用文件的实例而不是类 File 的实例
E.g.
例如
File file = ...;
field.setChar(file,'/');
You could also try http://code.google.com/p/jmockit/and mock the static method FileSystem.getFileSystem(). (don't know if you can mock static variables, normally those hacks shouldn't be necessary -> write oo code and use 'only' mockito)
您也可以尝试http://code.google.com/p/jmockit/并模拟静态方法 FileSystem.getFileSystem()。(不知道您是否可以模拟静态变量,通常不需要这些 hack -> 编写 oo 代码并使用“仅”模拟)
回答by Brian Agnew
I realise this doesn't answer your question directly, but Apache Commons FileNameUtilswill do cross-platform filename construction, and may save you writing your own class to do this.
我意识到这并不能直接回答您的问题,但Apache Commons FileNameUtils将进行跨平台文件名构建,并且可以节省您编写自己的类来执行此操作。
回答by Stephen Denne
You can take the source for java.io.File, and modify it so that separatorChar and separator are not final, and add a setSeparatorChar method that updates the two of them, then include the compiled class in your bootclasspath.
您可以获取 java.io.File 的源代码,并对其进行修改,使 separatorChar 和 separator 不是最终的,并添加一个更新它们两个的 setSeparatorChar 方法,然后将编译后的类包含在引导类路径中。
回答by user207421
Just use / everywhere when constructing Files. I've been doing that for 13 years and never had a problem. Nothing to test either.
构建文件时只需在任何地方使用 / 。我已经这样做了 13 年,从来没有遇到过问题。也没什么可考的。
回答by anand krish
here I am going to set value for "android.os.Build.VERSION.RELEASE", where VERSION is the class name and RELEASE is the final static string value.
在这里,我将为“android.os.Build.VERSION.RELEASE”设置值,其中 VERSION 是类名,RELEASE 是最终的静态字符串值。
If the underlying field is final, the method throwsan IllegalAccessExceptionso that we need to use setAccessible(true) , NoSuchFieldExceptionneeds to be added when you use field.set()method
如果基础字段是最后的,该方法抛出一个 IllegalAccessException,这样我们需要使用setAccessible(真), 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");
}
}
now the value of String RELEASEwill return "Marshmallow".
现在 String RELEASE的值将返回“ Marshmallow”。