2010-03-18 15 views
21

Específicamente, estoy intentando crear una prueba unitaria para un método que requiere el uso de 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.Usando la reflexión para cambiar el final estático File.separatorChar para la prueba unitaria?

¿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, me sale

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

Pensamientos?

+1

También podría ser mejor ejecutar las pruebas unitarias en un entorno VirtualBox bajo el otro sistema operativo objetivo. Quién sabe qué se romperá cuando te metas con la JVM así. Además, tal vez sea posible reescribir el código para no usar File.separatorChar directamente. Puede construir rutas usando el constructor File (parentFile, name), por ejemplo. – Thilo

+0

@Thilo: Es una buena idea, y ahora que lo pienso, probablemente exista una manera de ejecutar mi lógica de forma multiplataforma sin tener que lidiar con las URL de los archivos en absoluto. Creo que saber cómo cambiar java.io.File.separatorChar, sin embargo, es útil para saber si hay algún otro caso de uso legítimo. –

Respuesta

59

De la documentación para Field.set:

Si el campo subyacente es definitiva, el método produce una IllegalAccessException menos setAccessible(true) ha tenido éxito para este campo y este campo es no estático.

Parece que no tiene suerte, ya que File.separatorChar es static. Sorprendentemente, hay es una forma de evitar esto: simplemente haga que el campo static ya no sea final a través de la reflexión.

que esta solución adaptada 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); 
} 

Lo he probado y funciona:

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

Haz tenga mucho cuidado con esta técnica.consecuencias devastadoras aparte, la siguiente realmente funciona:

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

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

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

#define FALSE 1 está de vuelta, y está listo para patear un asno una vez más. –

+0

Finalmente puedo probar la unidad contra la fluctuación cuántica. –

+3

¡Santa mierda! La introducción de setAccessible es un gran problema. –

0

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.

2

Try invocando en una instancia de archivo no en una instancia de clase File

P. ej

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

También puede probar http://code.google.com/p/jmockit/ y burlarse del 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)

1

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

+0

Estoy deconstruyendo una ruta, en lugar de construir, pero lo que podría hacer es convertir todo a separadores de Unix, ejecutar mi algoritmo y luego convertir a separadores de sistema. De esta manera, puedo probar sin necesidad de burlar java.io.File. –

+0

Terminé usando esta solución, pero la respuesta a continuación respondió a mi pregunta inicial, así que en el espíritu de stackoverflow, acepté la otra respuesta. –

0

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

+0

Es posible que tenga que extender esa capacidad a todas las implementaciones de java.io.FileSystem. –

+0

Nota: Las aplicaciones que usan esta opción para anular una clase en rt.jar no deben implementarse, ya que esto contravendría la licencia del código binario de Java 2 Runtime Environment. –

2

sólo tiene que utilizar/en todas partes en la construcción de archivos. He estado haciendo eso durante 13 años y nunca tuve un problema. Nada para probar tampoco.

0

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 definitiva, el método tiros un IllegalAccessException de manera que tenemos que utilizar setAccessible (verdadero), NoSuchFieldException necesita ser añadido cuando se utiliza field.set () método

@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 cadena RELEASE devolverá "Marshmallow".

Cuestiones relacionadas