使用 Android 测试框架进行 Android AsyncTask 测试

发布于 2024-08-23 02:12:28 字数 3276 浏览 1 评论 0原文

我有一个非常简单的 AsyncTask 实现示例,但在使用 Android JUnit 框架测试它时遇到问题。

当我在正常应用程序中实例化并执行它时,它工作得很好。 但是,当它从任何 Android 测试框架类(即 AndroidTestCaseActivityUnitTestCaseActivityInstrumentationTestCase2 等)它的行为很奇怪:

  • 它正确执行 doInBackground() 方法
  • ,但是它不调用任何通知方法 (onPostExecute()、onProgressUpdate() 等)——只是默默地忽略它们而不显示任何错误。

这是非常简单的 AsyncTask 示例:

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

}

这是一个测试用例。这里AsyncTaskDemoActivity是一个非常简单的Activity,提供用于在模式下测试AsyncTask的UI:

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在Android测试框架内执行时不会调用它的通知方法。有什么想法吗?

I have a very simple AsyncTask implementation example and am having problem in testing it using Android JUnit framework.

It works just fine when I instantiate and execute it in normal application.
However when it's executed from any of Android Testing framework classes (i.e. AndroidTestCase, ActivityUnitTestCase, ActivityInstrumentationTestCase2 etc) it behaves strangely:

  • It executes doInBackground() method correctly
  • However it doesn't invokes any of its notification methods (onPostExecute(), onProgressUpdate(), etc) -- just silently ignores them without showing any errors.

This is very simple AsyncTask example:

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

}

This is a test case. Here AsyncTaskDemoActivity is a very simple Activity providing UI for testing AsyncTask in 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());
}

}

All this code is working just fine, except the fact that AsyncTask doesn't invoke it's notification methods when executed by within Android Testing Framework. Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

末が日狂欢 2024-08-30 02:12:28

我在实现一些单元测试时遇到了类似的问题。我必须测试一些与 Executors 一起使用的服务,并且我需要将我的服务回调与我的 ApplicationTestCase 类中的测试方法同步。通常测试方法本身在访问回调之前完成,因此不会测试通过回调发送的数据。尝试应用@UiThreadTest半身像仍然不起作用。

我发现了以下方法,有效,并且我仍然在使用。我只是使用 CountDownLatch 信号对象来实现等待通知(您可以使用 synchronized(lock){... lock.notify();},但这会导致代码丑陋)机制。

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
}

I met a similar problem while implementing some unit-test. I had to test some service which worked with Executors, and I needed to have my service callbacks sync-ed with the test methods from my ApplicationTestCase classes. Usually the test method itself finished before the callback would be accessed, so the data sent via the callbacks would not be tested. Tried applying the @UiThreadTest bust still didn't work.

I found the following method, which worked, and I still use it. I simply use CountDownLatch signal objects to implement the wait-notify (you can use synchronized(lock){... lock.notify();}, however this results in ugly code) mechanism.

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
}
羅雙樹 2024-08-30 02:12:28

我找到了很多接近的答案,但没有一个答案将所有部分正确地组合在一起。因此,在 JUnit 测试用例中使用 android.os.AsyncTask 时,这是一种正确的实现。

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

I found a lot of close answers but none of them put all the parts together correctly. So this is one correct implementation when using an android.os.AsyncTask in your JUnit tests cases.

 /**
 * 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);
}
仅此而已 2024-08-30 02:12:28

处理这个问题的方法是运行 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());
}

默认情况下,junit 在与主应用程序 UI 不同的单独线程中运行测试。 AsyncTask 的文档说任务实例和对execute() 的调用必须在主UI 线程上;这是因为 AsyncTask 依赖于主线程的 Looper 和 MessageQueue 来使其内部处理程序正常工作。

注意:

我之前建议使用 @UiThreadTest 作为测试方法上的装饰器,以强制测试在主线程上运行,但这对于测试 AsyncTask 来说不太正确,因为虽然您的测试方法是在主线程上运行时,主 MessageQueue 上不会处理任何消息 - 包括 AsyncTask 发送的有关其进度的消息,从而导致测试挂起。

The way to deal with this is to run any code that invokes an AsyncTask in 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());
}

By default junit runs tests in a separate thread than the main application UI. AsyncTask's documentation says that the task instance and the call to execute() must be on the main UI thread; this is because AsyncTask depends on the main thread's Looper and MessageQueue for its internal handler to work properly.

NOTE:

I previously recommended using @UiThreadTest as a decorator on the test method to force the test to run on the main thread, but this isn't quite right for testing an AsyncTask because while your test method is running on the main thread no messages are processed on the main MessageQueue — including the messages the AsyncTask sends about its progress, causing your test to hang.

时光与爱终年不遇 2024-08-30 02:12:28

如果您不介意在调用者线程中执行 AsyncTask(在单元测试的情况下应该没问题),您可以在当前线程中使用 Executor,如 https://stackoverflow.com/a/6583868/1266123

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

然后你在单元测试中运行 AsyncTask,如下所示

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

这仅适用于 HoneyComb 和更高。

If you don't mind executing the AsyncTask in the caller thread (should be fine in case of Unit testing), you can use an Executor in the current thread as described in https://stackoverflow.com/a/6583868/1266123

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

And then you run your AsyncTask in your unit test like this

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

This is only working for HoneyComb and higher.

狼性发作 2024-08-30 02:12:28

我为 Android 编写了足够多的unitests,只是想分享一下如何做到这一点。

首先,这里是负责等待和释放服务员的辅助类。没什么特别的:

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

接下来,让我们创建一个接口,其中包含一个方法,工作完成后应从 AsyncTask 调用该方法。当然,我们还想测试我们的结果:

TestTaskItf

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

接下来让我们创建我们要测试的任务的一些框架:

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

最后 - 我们的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)
    }
}

就这样。

希望它会对某人有所帮助。

I wrote enough unitests for Android and just want to share how to do that.

First off, here is helper class that responsible to wait and release waiter. Nothing special:

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

Next, lets create interface with one method that should be called from AsyncTask when work is done. Sure we also want to test our results:

TestTaskItf

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

Next lets create some skeleton of our Task that we gonna test:

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

At last - our unitest class:

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

That's all.

Hope it will help to someone.

感情洁癖 2024-08-30 02:12:28

如果您想测试 doInBackground 方法的结果,可以使用此方法。重写 onPostExecute 方法并在那里执行测试。要等待 AsyncTask 完成,请使用 CountDownLatch。 latch.await() 等待倒计时从 1(在初始化期间设置)到 0(由 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();
    }

This can be used if you want to test the result from the doInBackground method. Override the onPostExecute method and perform the tests there. To wait for the AsyncTask to complete use CountDownLatch. The latch.await() waits till the countdown runs from 1 (which is set during initialization) to 0 (which is done by the countdown() method).

@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();
    }
世界如花海般美丽 2024-08-30 02:12:28

使用 join 怎么样?

fun myTest() = runBlocking {
    CoroutineScope(Dispatchers.IO).launch {
        // test something here
    }.join()
}

How about using join?

fun myTest() = runBlocking {
    CoroutineScope(Dispatchers.IO).launch {
        // test something here
    }.join()
}
滥情稳全场 2024-08-30 02:12:28

使用这个简单的解决方案

runBlocking{
   //Your code here
}

Use this simple solution

runBlocking{
   //Your code here
}
花之痕靓丽 2024-08-30 02:12:28

大多数解决方案都需要为每个测试编写大量代码或更改类结构。如果您的项目中有很多正在测试的情况或很多 AsyncTasks,我发现它很难使用。

有一个可以简化AsyncTask的测试过程。示例:

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

基本上,它会运行 AsyncTask 并测试调用 postComplete() 后返回的结果。

Most of those solutions require a lot of code to be written for every test or to change your class structure. Which I find very difficult to use if you have many situations under test or many AsyncTasks on your project.

There is a library which eases the process of testing AsyncTask. Example:

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

Basically, it runs your AsyncTask and test the result it returns after the postComplete() has been called.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文