EclEmma引起反射相关测试失败的问题
背景说明
今天在运行项目的单元测试覆盖率时,有一处测试报错失败了,但是在正常运行单元测试时却可以通过,报错信息如下:
java.lang.IllegalAccessException: Class com.szyciov.sewx.util.TooltipsTool can not access a member of class com.szyciov.sewx.util.Tooltips with modifiers "private static final transient" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Field.set(Field.java:761) at com.szyciov.sewx.util.TooltipsTool.init(TooltipsTool.java:18) at com.szyciov.sewx.util.Tooltips.<clinit>(Tooltips.java:6) at sun.misc.Unsafe.ensureClassInitialized(Native Method) at sun.reflect.UnsafeFieldAccessorFactory.newFieldAccessor(UnsafeFieldAccessorFactory.java:43) at sun.reflect.ReflectionFactory.newFieldAccessor(ReflectionFactory.java:142) at java.lang.reflect.Field.acquireFieldAccessor(Field.java:1088) at java.lang.reflect.Field.getFieldAccessor(Field.java:1069) at java.lang.reflect.Field.get(Field.java:393) at com.szyciov.sewx.util.TooltipsToolTest.testGetKey_success(TooltipsToolTest.java:16) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Tooltips类,一系列静态字段,用于引用提示信息,如下:
public class Tooltips { static { TooltipsTool.init(); } public static String DictNotExist; public static String DictTypeEmpty; public static String GlobalDatabaseOperateFailed; //省略其他类似字段 }
TooltipsTool类,用于对从属性文件中读出提示信息,并对Tooltips类的字段进行初始化,如下:
public class TooltipsTool { public static void init() { Properties p = getProperties(); Field[] fields = Tooltips.class.getDeclaredFields(); for (Field field : fields) { try { field.set(null, getKey(p, field.getName())); //System.out.println(field.getName() + "=" + field.get(null)); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
TooltipsToolTest测试类,主要就是测试Tooltips类的所有字段不为空,如下:
public class TooltipsToolTest { @Test public void testGetKey_success() { Field[] fields = Tooltips.class.getDeclaredFields(); for (Field field : fields) { try { String value = (String) field.get(null); if (value == null || "".equals(value)) { fail(field.getName() + "属性值为空"); } } catch (IllegalArgumentException e) { fail(e.getMessage()); } catch (IllegalAccessException e) { fail(e.getMessage()); } } } }
解决方案
一开始我怀疑是那段static的代码背后生成了什么隐藏字段引起,但是后来打印错误信息,发现是叫$jacocoData的字段,百度了一下,发现jacoco(Java Code Coverage)就是EclEmma覆盖率检测工具所使用的技术,那么这个问题肯定就是由EclEmma工具本身引起。
错误提示信息也很明确,这个隐藏字段的修饰符是private static final transient,那么我就想着我只要使用field.isAccessible()来过滤掉这个字段,相关反射操作就不会再操作这个隐藏字段,而我的其他字段都是public的,应该就可以了。
但是经过测试发现,我所有的public字段的field.isAccessible()返回都是false,这是怎么回事?我只能猜测是static修饰符的作用,现在也没有查到相关的理论证明。
最后,换个方式,将Tooltips.class.getDeclaredFields()
换成Tooltips.class.getFields()
。
getDeclaredFields()返回Class中所有的字段,包括私有字段。
getFields() 只返回公共字段,即有public修饰的字段。
完!