Android Test Framework ile Android AsyncTask testi


97

Çok basit bir AsyncTask uygulama örneğim var ve Android JUnit çerçevesini kullanarak test etmede sorun yaşıyorum.

Normal uygulamada başlattığımda ve çalıştırdığımda gayet iyi çalışıyor. Bununla birlikte, herhangi bir Android Test çerçevesi sınıfından (yani AndroidTestCase , ActivityUnitTestCase , ActivityInstrumentationTestCase2 vb.) Yürütüldüğünde garip davranır:

  • doInBackground()Yöntemi doğru bir şekilde yürütür
  • Ancak onun bildirim yöntemleri (herhangi çağırır vermez onPostExecute(), onProgressUpdate()vs.) - sadece sessizce herhangi bir hata göstermeden onları yok sayar.

Bu çok basit AsyncTask örneğidir:

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

}

Bu bir test durumudur. Burada AsyncTaskDemoActivity , AsyncTask modunda kipte test etmek için UI sağlayan çok basit bir Etkinliktir:

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

}

AsyncTask'ın Android Testing Framework tarafından çalıştırıldığında bildirim yöntemlerini çağırmaması dışında tüm bu kod gayet iyi çalışıyor. Herhangi bir fikir?

Yanıtlar:


125

Bazı birim testi uygularken benzer bir sorunla karşılaştım. Executors ile çalışan bazı hizmetleri test etmem ve hizmet geri aramalarımın ApplicationTestCase sınıflarımdaki test yöntemleriyle senkronize edilmesini sağlamam gerekiyordu. Geri aramaya erişilmeden önce genellikle test yönteminin kendisi biter, bu nedenle geri aramalar yoluyla gönderilen veriler test edilmez. @UiThreadTest büstünü uygulamaya çalıştım hala işe yaramadı.

İşe yarayan aşağıdaki yöntemi buldum ve hala kullanıyorum. Wait-notify (senkronize edilmiş (lock) {... lock.notify ();} kullanabilirsiniz, ancak bu çirkin kodla sonuçlanır) mekanizmasını uygulamak için CountDownLatch sinyal nesnelerini kullanıyorum.

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
}

1
Nedir Service.doSomething()?
Peter Ajtai

11
AsynchTask'ı test ediyorum. Bunu yaptım ve arka plan görevi asla çağrılmamış gibi görünüyor ve singnal sonsuza kadar bekliyor :(
Ixx

@Ixx, aradın task.execute(Param...)önce await()ve koyun countDown()içinde onPostExecute(Result)? (bkz. stackoverflow.com/a/5722193/253468 ) Ayrıca @PeterAjtai, Service.doSomethingzaman uyumsuz bir çağrıdır task.execute.
TWiStErRob

ne güzel ve basit bir çözüm.
Maciej Beimcik

Service.doSomething()hizmet / zaman uyumsuz görev çağrınızı değiştirmeniz gereken yerdir. signal.countDown()Uygulamanız gereken herhangi bir yöntemi çağırdığınızdan emin olun, aksi takdirde testiniz takılıp kalır.
Victor R. Oliveira

94

Pek çok yakın cevap buldum ama hiçbiri tüm parçaları doğru bir şekilde bir araya getirmedi. Bu, JUnit test durumlarınızda android.os.AsyncTask kullanırken doğru bir uygulamadır.

 /**
 * 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);
}

1
tam bir örnek yazdığınız için teşekkürler ... Bunu uygularken çok sayıda küçük sorun yaşıyordum.
Peter Ajtai

1 yıldan biraz daha uzun bir süre sonra ve beni kurtardın. Teşekkürler Billy Brackeen!
MaTT

8
Bir zaman aşımının test başarısızlığı olarak sayılmasını istiyorsanız, şunları yapabilirsiniz:assertTrue(signal.await(...));
Jarett Millard

4
Hey Billy Bu uygulamayı denedim ama runTestOnUiThread bulunamadı. Test senaryosu AndroidTestCase'i genişletmeli mi yoksa ActivityInstrumentationTestCase2'yi genişletmesi mi gerekiyor?
Doug Ray

3
@DougRay Aynı sorunu yaşadım - InstrumentationTestCase'i genişletirseniz, runTestOnUiThread bulunacaktır.
Armatür

25

Bununla başa çıkmanın yolu, içinde bir AsyncTask'ı çağıran herhangi bir kodu çalıştırmaktır 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());
}

Varsayılan olarak junit testleri ana uygulama kullanıcı arayüzünden ayrı bir iş parçacığında çalıştırır. AsyncTask'ın belgeleri, görev örneğinin ve execute () çağrısının ana UI iş parçacığında olması gerektiğini söyler; AsyncTask ana iş parçacığı en bağlı olduğundan, bu Looperve MessageQueueiç işleyicisi düzgün çalışması için.

NOT:

Daha önce @UiThreadTest, testi ana iş parçacığı üzerinde çalıştırmaya zorlamak için test yönteminde bir dekoratör olarak kullanılmasını tavsiye ettim , ancak bu bir AsyncTask'ı test etmek için pek doğru değil çünkü test yönteminiz ana iş parçacığı üzerinde çalışırken hiçbir ileti işlenmez. ana MessageQueue - AsyncTask'ın ilerlemesi hakkında gönderdiği mesajlar da dahil olmak üzere testinizin askıda kalmasına neden olur.


Bu beni kurtardı ... başka bir iş parçacığından "runTestOnUiThread" i çağırmam gerekse de, aksi takdirde "Bu yöntem ana uygulama iş parçacığından çağrılamaz" mesajı alırdım
Matthieu

@Matthieu Dekoratörle runTestOnUiThread()bir test yönteminden mi kullandınız @UiThreadTest? Bu işe yaramaz. Bir test yöntemi yoksa @UiThreadTest, varsayılan olarak kendi ana olmayan iş parçacığı üzerinde çalışmalıdır.
Alex Pretzlav

1
Bu cevap saf mücevherdir. Güncellemeyi vurgulamak için yeniden çalışılmalıdır, eğer gerçekten ilk cevabı tutmak istiyorsanız, bunu biraz arka plan açıklaması ve ortak bir tuzak olarak koyun.
Snicolas

1
Belgelerde yöntem Deprecated in API level 24 geliştirici belirtilir.android.com/reference/android/test/…
Aivaras

1
Kullanımdan kaldırıldı, InstrumentationRegistry.getInstrumentation().runOnMainSync()onun yerine kullanın!
Marco7757

5

Çağıran iş parçacığında AsyncTask'ı yürütmeyi düşünmüyorsanız (Birim testi durumunda iyi olmalı), https://stackoverflow.com/a/6583868/1266123 adresinde açıklandığı gibi mevcut iş parçacığında bir Yürütücü kullanabilirsiniz.

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

Ve sonra AsyncTask'ınızı birim testinizde şu şekilde çalıştırırsınız

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

Bu yalnızca HoneyComb ve üzeri için çalışıyor.


bu yükselmeli
Stefan

5

Android için yeterince birim yazdım ve sadece bunun nasıl yapılacağını paylaşmak istiyorum.

Öncelikle, garsonu beklemek ve serbest bırakmaktan sorumlu yardımcı sınıf burada. Özel birşey yok:

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

Sonra, AsyncTaskiş bittiğinde çağrılması gereken bir yöntemle arayüz oluşturalım . Elbette sonuçlarımızı da test etmek istiyoruz:

TestTaskItf

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

Ardından, test edeceğimiz Görevimizin bir iskeletini oluşturalım:

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

Sonunda - unitest sınıfımız:

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

Bu kadar.

Umarım birine yardımcı olur.


4

doInBackgroundYöntemin sonucunu test etmek istiyorsanız bu kullanılabilir. onPostExecuteYöntemi geçersiz kılın ve testleri orada gerçekleştirin. AsyncTask'ın tamamlanmasını beklemek için CountDownLatch kullanın. latch.await()Bekler (yapılır 0 (başlatma sırasında ayarlanır) 1 ila geri sayım bitene kadar countdown()yöntemi).

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

-1

Bu çözümlerin çoğu, her test için veya sınıf yapınızı değiştirmek için çok fazla kod yazılmasını gerektirir. Projenizde test edilen birçok durum veya birçok AsyncTasks varsa bunu kullanmak çok zor.

Test sürecini kolaylaştıran bir kütüphane var AsyncTask. Misal:

@Test
  public void makeGETRequest(){
        ...
        myAsyncTaskInstance.execute(...);
        AsyncTaskTest.build(myAsyncTaskInstance).
                    run(new AsyncTest() {
                        @Override
                        public void test(Object result) {
                            Assert.assertEquals(200, (Integer)result);
                        }
                    });         
  }       
}

Temel olarak, AsyncTask'ınızı çalıştırır ve çağrıldıktan sonra döndürdüğü sonucu test eder postComplete().

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.