java unit-testing reflection file-io

java - ¿Usar el reflejo para cambiar el final estático File.separatorChar para la prueba unitaria?



unit-testing reflection (7)

Específicamente, estoy intentando crear una prueba unitaria para un método que requiere que use File.separatorChar para construir rutas en Windows y Unix. El código debe ejecutarse en ambas plataformas, y aun así obtengo errores con JUnit cuando intento cambiar este campo final estático.

Alguien tiene alguna idea de lo que está pasando?

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

Cuando hago esto, obtengo

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

¿Pensamientos?


De la documentación para Field.set :

Si el campo subyacente es final, el método arroja una IllegalAccessException menos que setAccessible(true) haya tenido éxito para este campo y este campo no sea estático .

Así que al principio parece que no tienes suerte, ya que File.separatorChar es static . Sorprendentemente, hay una forma de evitar esto: simplemente hacer que el campo static ya no sea final mediante la reflexión.

Adapte esta solución de 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); }

Lo he probado y funciona:

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

Tenga mucho cuidado con esta técnica . Dejando las consecuencias devastadoras, lo siguiente realmente funciona:

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

Actualización importante : la solución anterior no funciona en todos los casos. Si el campo es accesible y se lee a través de Reflection antes de que se restablezca, se lanza una IllegalAccessException . No funciona porque la API Reflection crea objetos internos de FieldAccessor que se almacenan en caché y se FieldAccessor utilizar (consulte la implementación de java.lang.reflect.Field # acquireFieldAccessor (boolean)). Ejemplo de código de prueba que falla:

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


En lugar de usar File.separatorChar, declare su clase de servicio, vamos a llamarlo PathBuilder o algo así. Esta clase tendrá un método concatPaths () que concatenará los dos parámetros (usando el carácter separador del sistema operativo). La belleza es que estás escribiendo esta clase para que puedas modificarla de cualquier manera que quieras cuando la pruebes por una unidad.


Intente invocar en una instancia de archivo no en una instancia de clase Archivo

P.ej

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

También puede probar http://code.google.com/p/jmockit/ y simular el método estático FileSystem.getFileSystem (). (No sé si puedes simular variables estáticas, normalmente esos hacks no deberían ser necesarios -> escribir código oo y usar ''solo'' mockito)


Me doy cuenta de que esto no responde su pregunta directamente, pero Apache Commons FileNameUtils hará la construcción de nombre de archivo multiplataforma, y ​​puede ahorrarle escribir su propia clase para hacer esto.


Puede tomar el origen de java.io.File y modificarlo para que separatorChar y separator no sean definitivos, y agregue un método setSeparatorChar que los actualice a los dos, luego incluya la clase compilada en su bootclasspath.


Solo use / en todas partes al construir archivos. He estado haciendo eso durante 13 años y nunca tuve un problema. Nada para probar tampoco.


aquí voy a establecer el valor para "android.os.Build.VERSION.RELEASE", donde VERSION es el nombre de la clase y RELEASE es el valor final de la cadena estática.

Si el campo subyacente es final, el método arroja una IllegalAccessException para que necesitemos usar setAccessible (verdadero), NoSuchFieldException debe agregarse cuando use el método 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"); } }

ahora el valor de String RELEASE devolverá " Marshmallow ".