100 votes

Tests AsyncTask Android avec Android Test Framework

J'ai une question très simple AsyncTask exemple de mise en œuvre et le problème de test à l'aide d'Android framework JUnit. Il fonctionne très bien lorsque j'instancie et de l'exécuter dans une application normale. Toutefois, lorsqu'il est exécuté à partir de Android cadre de Tests de classes (c'est à dire AndroidTestCase, ActivityUnitTestCase, ActivityInstrumentationTestCase2 etc) se comporte de manière étrange: - Il exécute doInBackground() la méthode correctement - Cependant, il n'invoque aucune de ses méthodes de notification (onPostExecute(), onProgressUpdate(), etc) -- ignore silencieusement sans montrer toutes les erreurs.

C'est très simple AsyncTask exemple

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");
}

}

C'est un cas de test. Ici AsyncTaskDemoActivity est très simple de l'Activité de fourniture de l'INTERFACE utilisateur pour les tests AsyncTask en mode:

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());
}

}

Tout ce code fonctionne très bien, sauf le fait que AsyncTask n'invoquent pas c'est les méthodes de notification lorsqu'il est exécuté par l'intérieur Android Framework de Test. Des idées?

127voto

bandi Points 296

J'ai rencontré un problème similaire lors de l'exécution de certains tests unitaires. J'ai eu à tester un certain nombre de services qui ont travaillé avec le maître d'oeuvre, et je devais avoir mes rappels de service de synchronisation-ed avec les méthodes de test à partir de mon ApplicationTestCase classes. Habituellement, la méthode d'analyse elle-même finie avant que la fonction de rappel devrait être accessible, de sorte que les données envoyées par les rappels de ne pas être contrôlés. Essayé l'application de la @UiThreadTest buste ne fonctionne toujours pas.

J'ai trouvé la méthode suivante, qui a travaillé, et j'ai toujours l'utiliser. J'utilise simplement CountDownLatch signal d'objets à mettre en œuvre l'attente de notification (vous pouvez utiliser synchronisée(lock){... de verrouillage.notify();}, cependant cette résultats dans laide code) mécanisme.

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
}

95voto

Billy Brackeen Points 411

J'ai trouvé beaucoup de réponses proches, mais aucune d'entre elles ne rassemble correctement toutes les parties. Il s’agit donc d’une implémentation correcte lorsqu’on utilise un android.os.AsyncTask dans vos tests 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);
}
 

25voto

Alex Pretzlav Points 2475

La façon de traiter cette question est d'exécuter n'importe quel code qui appelle une 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());
}

Par défaut exécute les tests junit dans un thread séparé de la principale INTERFACE utilisateur de l'application. AsyncTask de la documentation dit que la tâche de l'instance et de l'appel à execute() doit être sur le main thread d'INTERFACE utilisateur; c'est parce que AsyncTask dépend le thread principal de l' Looper et MessageQueue de son interne gestionnaire pour fonctionner correctement.

NOTE:

Je l'ai déjà recommandé à l'aide d' @UiThreadTest comme décorateur sur la méthode d'essai pour forcer le test à exécuter sur le thread principal, mais ce n'est pas tout à fait juste pour tester un AsyncTask parce que pendant que votre méthode de test est en cours d'exécution sur le thread principal pas de messages sont traités sur le principal MessageQueue - y compris les messages les AsyncTask envoie sur sa progression, à l'origine de votre test à accrocher.

5voto

Maxim Shoustin Points 20035

J'ai écrit assez unitests pour Android et tout simplement envie de partager ce savoir-faire.

Tout d'abord, ici, c'est la classe helper que responsable d'attendre et de libération garçon. Rien de spécial:

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) {
            }
        }
    }
}

Ensuite, vous permet de créer une interface avec une méthode qui devrait être appelé à partir d' AsyncTask lorsque le travail est terminé. Sûr, nous voulons également de test nos résultats:

TestTaskItf

public interface TestTaskItf {
    public void onDone(ArrayList<Integer> list); // dummy data
}

Suivant vous permet de créer des squelette de notre Tâche que l'on va tester:

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
        }
    }
}

Enfin notre unitest classe:

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)
    }
}

C'est tout.

J'espère que ça aidera quelqu'un.

5voto

robUx4 Points 161

Si l'exécution de la tâche asynchrone dans le fil de l'appelant ne vous dérange pas (cela devrait aller en cas de test d'unité), vous pouvez utiliser un exécuteur dans le fil de discussion actuel, comme décrit dans https://stackoverflow.com/a/6583868/1266123.

 public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}
 

Et puis vous lancez votre AsyncTask dans votre test unitaire comme ceci

 myAsyncTask.executeOnExecutor(new CurrentThreadExecutor(), testParam);
 

Cela ne fonctionne que pour HoneyComb et supérieur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X