使用 Android 测试框架进行 Android AsyncTask 测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2321829/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android AsyncTask testing with Android Test Framework
提问by Vladimir Kroz
I have a very simple AsyncTask implementation example and am having problem in testing it using Android JUnit framework.
我有一个非常简单的 AsyncTask 实现示例,并且在使用 Android JUnit 框架对其进行测试时遇到了问题。
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, ActivityInstrumentationTestCase2etc) it behaves strangely:
当我在普通应用程序中实例化并执行它时,它工作得很好。但是,当它从任何 Android 测试框架类(即AndroidTestCase、ActivityUnitTestCase、ActivityInstrumentationTestCase2等)执行时,它的行为会很奇怪:
- 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.
- 它
doInBackground()
正确执行方法 - 然而,它没有任何调用它的通知方法(
onPostExecute()
,onProgressUpdate()
等) -只是默默忽略它们没有表现出任何错误。
This is very simple AsyncTask example:
这是一个非常简单的 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");
}
}
This is a test case. Here AsyncTaskDemoActivityis a very simple Activity providing UI for testing AsyncTask in mode:
这是一个测试用例。这里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());
}
}
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?
所有这些代码都工作得很好,除了 AsyncTask 在 Android 测试框架内执行时不会调用它的通知方法。有任何想法吗?
回答by bandi
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.
我在实施一些单元测试时遇到了类似的问题。我必须测试一些与 Executors 一起工作的服务,我需要让我的服务回调与来自我的 ApplicationTestCase 类的测试方法同步。通常测试方法本身在回调被访问之前完成,所以通过回调发送的数据不会被测试。尝试应用 @UiThreadTest bust 仍然没有用。
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.
我发现以下方法有效,我仍然使用它。我只是使用 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
}
回答by Billy Brackeen
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.
我找到了很多相近的答案,但没有一个能正确地将所有部分组合在一起。所以这是在 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);
}
回答by Alex Pretzlav
The way to deal with this is to run any code that invokes an AsyncTask in runTestOnUiThread()
:
解决这个问题的方法是运行任何调用 AsyncTask 的代码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.
默认情况下,junit 在与主应用程序 UI 不同的线程中运行测试。AsyncTask 的文档说任务实例和对 execute() 的调用必须在主 UI 线程上;这是因为的AsyncTask取决于主线程的Looper
,并MessageQueue
用于其内部处理程序才能正常工作。
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.
我之前建议使用@UiThreadTest
作为测试方法的装饰器来强制测试在主线程上运行,但这对于测试 AsyncTask 不太合适,因为当您的测试方法在主线程上运行时,不会在主线程上处理任何消息main MessageQueue - 包括 AsyncTask 发送的关于其进度的消息,导致您的测试挂起。
回答by robUx4
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
如果您不介意在调用者线程中执行 AsyncTask(在单元测试的情况下应该没问题),您可以在当前线程中使用 Executor,如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
然后像这样在单元测试中运行 AsyncTask
myAsyncTask.executeOnExecutor(new CurrentThreadExecutor(), testParam);
This is only working for HoneyComb and higher.
这仅适用于 HoneyComb 及更高版本。
回答by Maxim Shoustin
I wrote enough unitests for Android and just want to share how to do that.
我为 Android 编写了足够多的 unitest,只是想分享如何做到这一点。
First off, here is helper class that responsible to wait and release waiter. Nothing special:
首先,这是负责等待和释放服务员的助手类。没什么特别的:
SyncronizeTalker
同步Talker
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:
接下来,让我们使用一种方法创建接口,该方法应AsyncTask
在工作完成时调用。当然,我们也想测试我们的结果:
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:
最后 - 我们的 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)
}
}
That's all.
就这样。
Hope it will help to someone.
希望它会帮助某人。
回答by Keerthana S
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).
如果您想测试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();
}
回答by Leonardo Soares e Silva
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.
这些解决方案中的大多数都需要为每个测试编写大量代码或更改您的类结构。如果您的项目中有很多要测试的情况或很多 AsyncTasks,我觉得很难使用它。
There is a librarywhich eases the process of testing AsyncTask
. Example:
有一个库可以简化测试过程AsyncTask
。例子:
@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.
基本上,它运行您的 AsyncTask 并测试它在postComplete()
调用后返回的结果。