Prueba Android AsyncTask con Android Test Framework
Tengo un ejemplo muy simple de implementación de AsyncTask y tengo problemas para probarlo usando el framework JUnit de Android.
Funciona muy bien cuando instancio y lo ejecuto en la aplicación normal. Sin embargo, cuando se ejecuta desde cualquiera de las clases del framework Android Testing (es decir, AndroidTestCase , ActivityUnitTestCase , ActivityInstrumentationTestCase2 etc) se comporta de manera extraña: – Ejecuta el método doInBackground () correctamente – Sin embargo, no invoca ninguno de sus métodos de notificación ( onPostExecute () , onProgressUpdate () , Etc) – simplemente ignora silenciosamente sin mostrar ningún error.
- Excepción en el subproceso "main" java.lang.NoClassDefFoundError: junit / textui / ResultPrinter
- La prueba de Android JUnit de Simpe se cuelga en Eclipse
- ¿Cómo puedo crear una estructura de prueba en Android Studio?
- Cómo ejecutar pruebas en un fragmento de actividades
- Cuenta de simulacro en ActivityInstrumentationTestCase2
Esto es muy simple AsyncTask ejemplo:
package kroz.andcookbook.threads.asynctask; import android.os.AsyncTask; import android.util.Log; import android.widget.ProgressBar; import android.widget.Toast; public class AsyncTaskDemo extends AsyncTask<Integer, Integer, String> { AsyncTaskDemoActivity _parentActivity; int _counter; int _maxCount; public AsyncTaskDemo(AsyncTaskDemoActivity asyncTaskDemoActivity) { _parentActivity = asyncTaskDemoActivity; } @Override protected void onPreExecute() { super.onPreExecute(); _parentActivity._progressBar.setVisibility(ProgressBar.VISIBLE); _parentActivity._progressBar.invalidate(); } @Override protected String doInBackground(Integer... params) { _maxCount = params[0]; for (_counter = 0; _counter <= _maxCount; _counter++) { try { Thread.sleep(1000); publishProgress(_counter); } catch (InterruptedException e) { // Ignore } } } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); int progress = values[0]; String progressStr = "Counting " + progress + " out of " + _maxCount; _parentActivity._textView.setText(progressStr); _parentActivity._textView.invalidate(); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); _parentActivity._progressBar.setVisibility(ProgressBar.INVISIBLE); _parentActivity._progressBar.invalidate(); } @Override protected void onCancelled() { super.onCancelled(); _parentActivity._textView.setText("Request to cancel AsyncTask"); } }
Este es un caso de prueba. Aquí AsyncTaskDemoActivity es una actividad muy simple que proporciona UI para probar AsyncTask en modo:
package kroz.andcookbook.test.threads.asynctask; import java.util.concurrent.ExecutionException; import kroz.andcookbook.R; import kroz.andcookbook.threads.asynctask.AsyncTaskDemo; import kroz.andcookbook.threads.asynctask.AsyncTaskDemoActivity; import android.content.Intent; import android.test.ActivityUnitTestCase; import android.widget.Button; public class AsyncTaskDemoTest2 extends ActivityUnitTestCase<AsyncTaskDemoActivity> { AsyncTaskDemo _atask; private Intent _startIntent; public AsyncTaskDemoTest2() { super(AsyncTaskDemoActivity.class); } protected void setUp() throws Exception { super.setUp(); _startIntent = new Intent(Intent.ACTION_MAIN); } protected void tearDown() throws Exception { super.tearDown(); } public final void testExecute() { startActivity(_startIntent, null, null); Button btnStart = (Button) getActivity().findViewById(R.id.Button01); btnStart.performClick(); assertNotNull(getActivity()); } }
Todo este código está funcionando bien, excepto el hecho de que AsyncTask no invoca sus métodos de notificación cuando son ejecutados por Android Testing Framework. ¿Algunas ideas?
- Cómo probar el archivo java generado automáticamente usando JUnit para mi AIDL
- Uso de Mockito Matchers.any () con android.support.annotation.IntDef anotación personalizada
- Cobertura de código en android studio 1.2 para pruebas instrumentadas
- Gradle connectedAndroidTest falla con "No se han encontrado pruebas", pero muestra los resultados de las pruebas
- Android: pruebas de instumentación para widgets de aplicaciones
- Cómo personalizar gradle android test html report
- Probar rutinas de procesamiento de imágenes con robolectric
- Diferencia entre el método runOnUiThread () y la anotación @UiThreadTest
Me encontré con un problema similar al implementar algunas unidades de prueba. Tuve que probar algún servicio que funcionaba con los ejecutores, y necesitaba que mis servicios de devolución de llamada sincronizados con los métodos de prueba de mis clases ApplicationTestCase. Normalmente, el método de prueba terminó antes de que se accediera a la devolución de llamada, por lo que los datos enviados a través de las devoluciones de llamada no se probarían. Trató de aplicar el busto de @UiThreadTest todavía no funcionó.
He encontrado el siguiente método, que funcionó, y todavía lo uso. Simplemente uso objetos de señal CountDownLatch para implementar el wait-notify (se puede usar sincronizado (lock) {… lock.notify ();}, sin embargo esto resulta en código feo).
public void testSomething(){ final CountDownLatch signal = new CountDownLatch(1); Service.doSomething(new Callback() { @Override public void onResponse(){ // test response data // assertEquals(.. // assertTrue(.. // etc signal.countDown();// notify the count down latch } }); signal.await();// wait for callback }
He encontrado un montón de respuestas cercanas, pero ninguno de ellos poner todas las piezas juntas correctamente. Por lo tanto, esta es una implementación correcta al usar un android.os.AsyncTask en sus casos de pruebas de JUnit.
/** * This demonstrates how to test AsyncTasks in android JUnit. Below I used * an in line implementation of a asyncTask, but in real life you would want * to replace that with some task in your application. * @throws Throwable */ public void testSomeAsynTask () throws Throwable { // create a signal to let us know when our task is done. final CountDownLatch signal = new CountDownLatch(1); /* Just create an in line implementation of an asynctask. Note this * would normally not be done, and is just here for completeness. * You would just use the task you want to unit test in your project. */ final AsyncTask<String, Void, String> myTask = new AsyncTask<String, Void, String>() { @Override protected String doInBackground(String... arg0) { //Do something meaningful. return "something happened!"; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); /* This is the key, normally you would use some type of listener * to notify your activity that the async call was finished. * * In your test method you would subscribe to that and signal * from there instead. */ signal.countDown(); } }; // Execute the async task on the UI thread! THIS IS KEY! runTestOnUiThread(new Runnable() { @Override public void run() { myTask.execute("Do something"); } }); /* The testing thread will wait here until the UI thread releases it * above with the countDown() or 30 seconds passes and it times out. */ signal.await(30, TimeUnit.SECONDS); // The task is done, and now you can assert some things! assertTrue("Happiness", true); }
La forma de hacer frente a esto es ejecutar cualquier código que invoca un AsyncTask en runTestOnUiThread()
:
public final void testExecute() { startActivity(_startIntent, null, null); runTestOnUiThread(new Runnable() { public void run() { Button btnStart = (Button) getActivity().findViewById(R.id.Button01); btnStart.performClick(); } }); assertNotNull(getActivity()); // To wait for the AsyncTask to complete, you can safely call get() from the test thread getActivity()._myAsyncTask.get(); assertTrue(asyncTaskRanCorrectly()); }
De forma predeterminada, junit ejecuta pruebas en un subproceso distinto de la interfaz de usuario principal de la aplicación. La documentación de AsyncTask dice que la instancia de tarea y la llamada a execute () deben estar en el subproceso de UI principal; Esto es porque AsyncTask depende de Looper
del subproceso principal y MessageQueue
para que su controlador interno funcione correctamente.
NOTA:
Anteriormente recomendé usar @UiThreadTest
como decorador en el método de prueba para forzar la ejecución de la prueba en el subproceso principal, pero esto no es correcto para probar un AsyncTask porque mientras el método de prueba se ejecuta en el subproceso principal no se procesan mensajes En el MessageQueue principal – incluyendo los mensajes que el AsyncTask envía sobre su progreso, haciendo que su prueba se bloquee.
Si no le importa ejecutar AsyncTask en el hilo de la persona que llama (debería estar bien en caso de prueba de unidad), puede usar un Ejecutor en el hilo actual como se describe en https://stackoverflow.com/a/6583868/1266123
public class CurrentThreadExecutor implements Executor { public void execute(Runnable r) { r.run(); } }
Y luego ejecuta el AsyncTask en su unidad de prueba como este
myAsyncTask.executeOnExecutor(new CurrentThreadExecutor(), testParam);
Esto sólo funciona para HoneyComb y superior.
Escribí suficientes unitests para Android y solo quiero compartir cómo hacerlo.
En primer lugar, aquí está la clase ayudante que el responsable de esperar y soltar el camarero. Nada especial:
SyncronizeTalker
public class SyncronizeTalker { public void doWait(long l){ synchronized(this){ try { this.wait(l); } catch(InterruptedException e) { } } } public void doNotify() { synchronized(this) { this.notify(); } } public void doWait() { synchronized(this){ try { this.wait(); } catch(InterruptedException e) { } } } }
A continuación, permite crear interfaz con un método que se debe llamar desde AsyncTask
cuando se realiza el trabajo. Claro que también queremos probar nuestros resultados:
TestTaskItf
public interface TestTaskItf { public void onDone(ArrayList<Integer> list); // dummy data }
A continuación vamos a crear un esqueleto de nuestra tarea que vamos a probar:
public class SomeTask extends AsyncTask<Void, Void, SomeItem> { private ArrayList<Integer> data = new ArrayList<Integer>(); private WmTestTaskItf mInter = null;// for tests only public WmBuildGroupsTask(Context context, WmTestTaskItf inter) { super(); this.mContext = context; this.mInter = inter; } @Override protected SomeItem doInBackground(Void... params) { /* .... job ... */} @Override protected void onPostExecute(SomeItem item) { // .... if(this.mInter != null){ // aka test mode this.mInter.onDone(data); // tell to unitest that we finished } } }
Por fin – nuestra clase unitest:
TestBuildGroupTask
public class TestBuildGroupTask extends AndroidTestCase implements WmTestTaskItf{ private SyncronizeTalker async = null; public void setUP() throws Exception{ super.setUp(); } public void tearDown() throws Exception{ super.tearDown(); } public void test____Run(){ mContext = getContext(); assertNotNull(mContext); async = new SyncronizeTalker(); WmTestTaskItf me = this; SomeTask task = new SomeTask(mContext, me); task.execute(); async.doWait(); // <--- wait till "async.doNotify()" is called } @Override public void onDone(ArrayList<Integer> list) { assertNotNull(list); // run other validations here async.doNotify(); // release "async.doWait()" (on this step the unitest is finished) } }
Eso es todo.
Espero que ayude a alguien.
Esto se puede utilizar si desea probar el resultado del método doInBackground
. onPostExecute
método onPostExecute
y realice las pruebas allí. Para esperar que el AsyncTask complete el uso CountDownLatch. El latch.await()
espera hasta que la cuenta regresiva se ejecute desde 1 (que se establece durante la inicialización) a 0 (que se realiza mediante el método countdown()
).
@RunWith(AndroidJUnit4.class) public class EndpointsAsyncTaskTest { Context context; @Test public void testVerifyJoke() throws InterruptedException { assertTrue(true); final CountDownLatch latch = new CountDownLatch(1); context = InstrumentationRegistry.getContext(); EndpointsAsyncTask testTask = new EndpointsAsyncTask() { @Override protected void onPostExecute(String result) { assertNotNull(result); if (result != null){ assertTrue(result.length() > 0); latch.countDown(); } } }; testTask.execute(context); latch.await(); }
La mayoría de estas soluciones requieren que se escriba mucho código para cada prueba o para cambiar la estructura de su clase. Lo cual me resulta muy difícil de usar si tienes muchas situaciones bajo prueba o muchas AsyncTasks en tu proyecto.
Hay una biblioteca ( https://github.com/lsoaresesilva/asynctasktest ) que facilita el proceso de prueba de AsyncTask
. Ejemplo:
@Test public void makeGETRequest(){ ... myAsyncTaskInstance.execute(...); AsyncTaskTest.build(myAsyncTaskInstance). run(new AsyncTest() { @Override public void test(Object result) { Assert.assertEquals(200, (Integer)result); } }); } }
Básicamente, ejecuta su AsyncTask y prueba el resultado que devuelve después de que el postComplete haya sido llamado.
- Referencia indefinida a `__android_log_print '
- Obtener objeto JSON anidado con GSON utilizando retrofit