2012-03-03 7 views
9

Tengo un caso de prueba JUnit en un proyecto de Android que contiene código que se parece a esto:miembro de 'private static final' de Android prueba de unidad de valor cambios de clase para anular

private static final URI TEST_RESOURCE_URL = TasksService.TASKLIST_RESOURCELIST_URL.resolve("task/test.task"); 

public void setUp() { 
    Log.i("Test", "TEST_RESOURCE_URL=" + TEST_RESOURCE_URL); 
} 

Esta clase de prueba tiene múltiples métodos de prueba , algunos de los cuales se refieren (pero no intentan modificar) el valor de esta constante. Sin embargo, cuando corro estas pruebas (Android 2.2.2), todas estas pruebas, pero el primero falla, y Logcat me muestra esto:

03-03 18:56:41.791: I/Test(12008): TEST_RESOURCE_URL=http://apate.meridiandigital.net/tasks/task/test.task 
03-03 18:56:42.101: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.131: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.151: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.281: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.311: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.341: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.361: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.391: I/Test(12008): TEST_RESOURCE_URL=null 
03-03 18:56:42.391: I/Test(12008): TEST_RESOURCE_URL=null 

¿Cómo funciona un valor de cambio de campo final estático como sigue? ¿Cómo evito que esto suceda? ¿Hay otras situaciones donde podría suceder?

--- EDIT 1

ahora han recortado el código abajo a un ejemplo más pequeño que se puede incluir en su totalidad. Ver más abajo:

public class MyService extends Service { 
    @Override 
    public IBinder onBind(Intent intent) { 
     return null; 
    } 
} 


public class StaticFinalTest extends ServiceTestCase<MyService> { 
    public StaticFinalTest() { 
     super(MyService.class); 
    } 

    public static final Object CONST2 = new Object(); 

    public void testA() 
    { 
     assertNotNull (CONST2); 
    } 

    public void testB() 
    { 
     assertNotNull (CONST2); 
    } 
} 

Cuando se ejecuta esta prueba, Testa pasa pero TESTB falla. Si testA está comentado, testB pasa.

Parece ser importante que sea una prueba de servicio. Un JUnit TestCase estándar no causa el problema. Si 'CONST2' es una cadena, ambas pruebas pasan como se esperaba. Cualquier otro tipo de referencia parece reproducir el problema.

+0

¿Se podría descargar y volver a cargar la clase de prueba? ¿Puede Dalvik incluso hacer eso? ¿Tal vez tu constante está siendo ensombrecida por otra cosa? – nmr

+0

No hay nada más del mismo nombre en ninguna parte del proyecto, así que no sospecho que se trata de un problema oculto. Sin embargo, creo que JUnit intenta volver a cargar clases entre ejecuciones de prueba utilizando un ClassLoader diferente para cada invocación. Entonces el campo * puede * ser una instancia diferente de la segunda ejecución en adelante.En cuyo caso, el problema es menos que haya cambiado, y más que el inicializador no se ejecutó por segunda vez. – Jules

+1

No, JUnit no vuelve a cargar clases, solo vuelve a crear instancias de la clase antes de cada método. –

Respuesta

7

Parece que AndroidTestCase utiliza la reflexión para configurar todos los campos no primitivos a null después de cada prueba en scrubClass. No verifica si los campos son estáticos o finales, por lo que esta parece ser la fuente del problema.

Para solucionarlo, cambie el campo a no final y configúrelo dentro de setUp. Además, asegúrese de llamar al super.setUp() como la primera línea de su setUp para asegurarse de que el estuche de prueba esté inicializado correctamente.

+0

Dalvik deja que esto suceda? Podrían otras JVMs permitir eso? Esto tampoco coincide con 'Parece ser importante que sea un ServiceTestCase. Un JUnit TestCase estándar no causa el problema. Si 'CONST2' es una cadena, ambas pruebas pasan como se esperaba. Cualquier otro tipo de referencia parece reproducir el problema '. – nmr

+2

Acabo de probarlo en una JVM normal, y 'setAccessible' no tiene ningún efecto; es decir, no puedo sobreescribir el valor final. Revisando 'scrubClass' nuevamente, no estoy seguro de qué es lo que hace. La documentación no parece coincidir con la implementación, pero no sé exactamente cómo se llama. Parece que solo anula los campos que son casos de prueba que parecen bastante extraños. Usaría la solución alternativa y lo dejaría ir a menos que realmente tenga curiosidad por profundizar más. :) –

+6

Resulta que este ha sido un error conocido en Android durante casi 3 años. http://code.google.com/p/android/issues/detail?id=4244 – Jules

0

Si no es un problema de alcance, es posible que esté anulando el valor al que se hace referencia.

private static final URI TEST_RESOURCE_URL = TasksService.TASKLIST_RESOURCELIST_URL.resolve("task/test.task"); 

así, si TasksService cambios, o la llamada a resolve() falla, que lo haría.

+0

TasksService.TASKLIST_RESOURCELIST_URL es en sí mismo una constante, por lo que no debería ser posible que esté cambiando. Y resolve() arroja IllegalArgumentException si falla, lo que debería conducir a un ExceptionInInitializerError, que no está sucediendo, por lo que tampoco parece ser el problema. – Jules

1

¿Es posible que el recolector de basura se deshaga de él porque no puede saber que todavía está accediendo al objeto usando JUnit?

Quizás deba crear un método que establezca el valor inicialmente. No olvide anotarlo con @Begin para asegurar la correcta inicialización de esta variable.

De todos modos, en un caso de prueba, no debe suponer que las operaciones anteriores tuvieron éxito. El entorno que necesita en una tarea JUnit debe acumularse desde cero cada vez.

respecto

+0

Si lo es, parece ser un error del recolector de basura. No debería ser posible para el GC recoger un objeto al que se pueda llegar (excepto a través de WeakRef <> y similares). Además, si esto sucediera, esperaría que la máquina virtual se bloquee, ya que no sabría que la referencia colgante aún no era válida, y trataría de seguirla en lugar de reemplazarla por nula. – Jules

+0

@Begin no se aplica; que parece ser una adición de JUnit 4, y Android utiliza JUnit 3. Y no estoy suponiendo que las operaciones anteriores tuvieron éxito. Simplemente estoy usando una constante que debe calcularse solo una vez en el tiempo de inicialización de la clase. – Jules

Cuestiones relacionadas