【问题标题】:Android AsyncTask testing with Android Test Framework使用 Android 测试框架进行 Android AsyncTask 测试
【发布时间】:2010-02-23 21:22:39
【问题描述】:

我有一个非常简单的 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 测试框架内执行时不会调用它的通知方法这一事实。有什么想法吗?

【问题讨论】:

    标签: android junit


    【解决方案1】:

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

    我找到了以下方法,它有效,我仍然使用它。我只是使用 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
    }
    

    【讨论】:

    • 什么是Service.doSomething()
    • 我正在测试一个异步任务。这样做了,而且,后台任务似乎永远不会被调用并且信号会永远等待:(
    • @Ixx,您是否在await() 之前调用了task.execute(Param...) 并将countDown() 放入onPostExecute(Result)? (参见stackoverflow.com/a/5722193/253468)另外@PeterAjtai,Service.doSomething 是一个异步调用,如task.execute
    • 多么可爱又简单的解决方案。
    • Service.doSomething() 是您应该替换服务/异步任务调用的地方。确保在您需要实现的任何方法上调用signal.countDown(),否则您的测试会卡住。
    【解决方案2】:

    我找到了很多相近的答案,但没有一个将所有部分正确地组合在一起。因此,在您的 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);
    }
    

    【讨论】:

    • 感谢您编写了一个完整的示例......我在实现这个时遇到了很多小问题。
    • 一年多之后,你救了我。谢谢比利·布莱肯!
    • 如果你想让超时算作测试失败,你可以这样做:assertTrue(signal.await(...));
    • 嘿比利我已经尝试过这个实现,但没有找到 runTestOnUiThread。测试用例应该扩展 AndroidTestCase 还是需要扩展 ActivityInstrumentationTestCase2?
    • @DougRay 我也遇到了同样的问题——如果你扩展 InstrumentationTestCase 然后会找到 runTestOnUiThread。
    【解决方案3】:

    解决这个问题的方法是运行任何在runTestOnUiThread() 中调用 AsyncTask 的代码:

    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 依赖于主线程的 LooperMessageQueue 以使其内部处理程序正常工作。

    注意:

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

    【讨论】:

    • 这救了我……虽然我不得不从另一个线程调用“runTestOnUiThread”,否则,我会得到“无法从主应用程序线程调用此方法”
    • @Matthieu 您是否在带有@UiThreadTest 装饰器的测试方法中使用runTestOnUiThread()?那是行不通的。如果一个测试方法没有@UiThreadTest,它应该默认运行在自己的非主线程上。
    • 这个答案是纯粹的宝石。应该重做以强调更新,如果您真的想保留最初的答案,请将其作为一些背景解释和常见的陷阱。
    • 文档状态方法Deprecated in API level 24developer.android.com/reference/android/test/…
    • 已弃用,请改用InstrumentationRegistry.getInstrumentation().runOnMainSync()
    【解决方案4】:

    如果您不介意在调用者线程中执行 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 及更高版本。

    【讨论】:

    • 这应该会上升
    【解决方案5】:

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

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

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

    最后——我们最团结的班级:

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

    就是这样。

    希望对某人有所帮助。

    【讨论】:

      【解决方案6】:

      如果您想测试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();
          }
      

      【讨论】:

        【解决方案7】:

        join怎么样?

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

        【讨论】:

          【解决方案8】:

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

          有一个library 可以简化测试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() 后返回的结果。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-16
            • 1970-01-01
            • 2013-08-18
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多