2011-02-11 14 views
17

Me falta algo aquí. El JavaDoc de ActivityUnitTestCase sugiere que este caso de prueba pruebe una Actividad aisladamente del sistema:Cómo evitar que ActivityUnitTestCase llame a Application.onCreate?

Esta clase proporciona pruebas aisladas de una sola actividad. La actividad bajo prueba se creará con una conexión mínima a la infraestructura del sistema, y ​​usted puede inyectar versiones engañadas o envueltas de muchas de las dependencias de Activity.

Estaba asumiendo que incluye no iniciar realmente la aplicación. Además, expone un auxiliar setApplication que uno puede presumiblemente utilizar para inyectar una aplicación simulada.

Sin embargo, cualquier ActivityUnitTestCase que inicie inicia la aplicación (real) y llama a su método onCreate. Más precisamente, el InstrumentationTestRunner parece estar haciendo eso, y hacerlo incluso antes de que tenga la oportunidad de setApplication en el método de prueba setUp! No lo noté durante bastante tiempo, ya que parece suceder en un punto durante el lanzamiento de la suite de pruebas donde ni siquiera se alcanzan los puntos de interrupción de Eclipse, pero escribir en los registros en onCreate revela que realmente se llama.

Esto me supera por completo. ¿Por qué querría usar un objeto de aplicación simulada cuando el corredor de prueba de Android ejemplifica y ejecuta la aplicación real de todos modos? Esto es aún más problemático teniendo en cuenta que el corredor de instrumentación se ejecuta en su propio hilo, y genera el hilo principal de la aplicación al hacerlo. Esto significa que hay una condición de carrera entre la ejecución de la prueba y el llamado al Application.onCreate. Si haces algo allí que pueda afectar tus pruebas, p. escribiendo en un archivo de preferencias compartido, entonces estás completamente jodido, ya que tus pruebas fallarán al azar.

¿Me falta algo o es simplemente una gran supervisión en el marco de prueba?

ACTUALIZACIÓN Esto también parece afectar ApplicationTestCase. Antes de que mi caso de prueba se haya iniciado, puedo alcanzar un punto de interrupción en mi clase de aplicación 'onCreate. Comenzamos un Fire-and-forget AsyncTask allí, que fallará al azar porque no tengo ninguna posibilidad de burlarlo (recuerde, eso es antes de que se llame al setUp en mi caso de prueba). Aquí está el seguimiento de la pila veo durante este oscuro invocación de onCreate:

Thread [<1> main] (Suspended (breakpoint at line 86 in QypeRadar)) 
QypeRadar.onCreate() line: 86 
InstrumentationTestRunner(Instrumentation).callApplicationOnCreate(Application) line: 969 
ActivityThread.handleBindApplication(ActivityThread$AppBindData) line: 4244 
ActivityThread.access$3000(ActivityThread, ActivityThread$AppBindData) line: 125  
ActivityThread$H.handleMessage(Message) line: 2071 
ActivityThread$H(Handler).dispatchMessage(Message) line: 99 
Looper.loop() line: 123 
ActivityThread.main(String[]) line: 4627  
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method] 
Method.invoke(Object, Object...) line: 521 
ZygoteInit$MethodAndArgsCaller.run() line: 868 
ZygoteInit.main(String[]) line: 626 
NativeStart.main(String[]) line: not available [native method] 

¿Por qué el corredor de prueba callApplicationOnCreate a pesar de que the docs claramente:

el caso de prueba no llamará onCreate() hasta que su llamadas de prueba createApplication(). Esto le da la oportunidad de configurar o ajustar cualquier marco adicional o lógica de prueba antes de onCreate().

Eso es totalmente rotundo, ¡no me da la oportunidad!

+0

Hasta donde puedo decir, solo hay dos maneras de solucionar esto: 1) reescribir InstrumentationTestRunner para no hacer esa llamada a 'callApplicationOnCreate' y dejarla en el caso de prueba para decidir si eso es necesario o no. Sin embargo, no estoy seguro del efecto que tendrá en otras pruebas. 2) Reescriba nuestro método onCreate para que sea libre de efectos secundarios e idempotente (recuerde que se llamará dos veces, ya que para que el caso de prueba funcione también debe llamar a 'createApplication'). ¿Pensamientos? – Matthias

+0

Por lo tanto, he creado un corredor de prueba personalizado que elude 'callApplicationOnCreate', ahora todo funciona como se espera para las pruebas de * unidad *. Para las pruebas funcionales, aún necesitaría el corredor de prueba predeterminado que realiza un lanzamiento completo, por lo que ahora tengo dos corredores de prueba, lo cual es un poco molesto. – Matthias

+0

Relato interesante. ¿Sería lo suficientemente simple pasar por alto 'callApplicationOnCreate' solo para los tipos de casos de prueba apropiados, lo que le permite crear un parche para el corredor de prueba que puede enviar en sentido ascendente? –

Respuesta

3

Roboguice tenía el mismo problema. Verifíquelo here.

+3

+1 para el material útil, ¡antes no sabía sobre 'attachBaseContext'! Sin embargo, no ayuda: el corredor de prueba todavía invoca el constructor sin argumentos, y si lo elimino, recibo una excepción: "InstantiationException - newInstance failed: no ()". Si dejo el código no-args, se sigue llamando al onCreate en el objeto de la aplicación original, independientemente de si llamo a setApplication o no, por lo que el problema persiste. – Matthias

+0

Si bien esto podría responder teóricamente a la pregunta, [sería preferible] (// meta.stackoverflow.com/q/8259) incluir aquí las partes esenciales de la respuesta y proporcionar el enlace de referencia. – nhaarman

2

Estoy haciendo pruebas con daga, por lo que probablemente este sea tu caso también, ya que probablemente quieras inyectar y no llamar a lo que esté en la aplicación.onCreate, así que éste es me da buenos resultados (api17 +):

private Context mContext; 
private Application mApplication; 

@Override 
protected void setUp() throws Exception { 
    super.setUp(); 

    mContext = new ContextWrapper(getInstrumentation().getTargetContext()) { 
     @Override 
     public Context getApplicationContext() { 
      return mApplication; 
     } 
    }; 
    mApplication = new MyAppMock(); 
    mApplication.attachBaseContext(mContext); 

    setApplication(app); 
} 

public void testActivityCreated() { 
    Intent intent = AboutActivity.createIntent(mContext); 
    setActivityContext(mContext); 
    startActivity(intent, null, null); 
    assertNotNull(getActivity()); 
} 

Para < api16 es necesario utilizar la reflexión y llamar Application.attach(context) lugar Application.attachBaseContext() para establecer Application.mLoadedApk, de lo contrario se bloqueará.

He puesto todo junto e hizo aplicación de demostración que muestra cómo probar con la daga: https://github.com/vovkab/dagger-unit-test

También muestra cómo se burlan de su aplicación, funciona para cualquier versión de Android.

+0

¿Ha podido probar la clase de prueba "Dagger-unit-test"? Intenté este enfoque yo mismo pero fallé porque Dagger se queja de que el TestModule no tiene la clase objetivo registrada. Está viendo la clase objetivo como la clase anónima MyTestClass $ 2 en lugar de InjectableAppMock (que es lo que se especifica en el atributo "inyectar" de los módulos su anotación de "Módulo" – twelve17

+0

No estoy seguro de qué código de muestra está hablando, los nombres de clase que mencionó no coinciden ni este ejemplo ni el ejemplo alojado en github. En mi aplicación de muestra alojada en github, inyecté la clase MainActivity: @Module (injects = MainActivity.class). Sí, funciona bien, por favor, eche un vistazo aquí, por ejemplo: https://github.com/vovkab/dagger-unit-test – vovkab

+0

Disculpas por no responder a esto antes. Obviamente debo haber tenido el código incorrecto - Acabo de regresar al proyecto en el que estaba trabajando, y me di cuenta de que me faltaba la llamada a ['setActivityContext (mContext);'] (https://github.com/vovkab/ dagger-unit-test/blob/master/app/s rc/androidTest/java/vovkab/daggerexample/MainActivityUnitTest.java # L59) de su ejemplo. ¡Gracias! – twelve17

Cuestiones relacionadas