使用 Android 测试框架进行 Android AsyncTask 测试
我有一个非常简单的 AsyncTask 实现示例,但在使用 Android JUnit 框架测试它时遇到问题。
当我在正常应用程序中实例化并执行它时,它工作得很好。 但是,当它从任何 Android 测试框架类(即 AndroidTestCase、ActivityUnitTestCase、ActivityInstrumentationTestCase2 等)它的行为很奇怪:
- 它正确执行
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
我在实现一些单元测试时遇到了类似的问题。我必须测试一些与 Executors 一起使用的服务,并且我需要将我的服务回调与我的 ApplicationTestCase 类中的测试方法同步。通常测试方法本身在访问回调之前完成,因此不会测试通过回调发送的数据。尝试应用@UiThreadTest半身像仍然不起作用。
我发现了以下方法,有效,并且我仍然在使用。我只是使用 CountDownLatch 信号对象来实现等待通知(您可以使用 synchronized(lock){... lock.notify();},但这会导致代码丑陋)机制。
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.
我找到了很多接近的答案,但没有一个答案将所有部分正确地组合在一起。因此,在 JUnit 测试用例中使用 android.os.AsyncTask 时,这是一种正确的实现。
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.
处理这个问题的方法是运行
runTestOnUiThread()
:默认情况下,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()
: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
andMessageQueue
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.如果您不介意在调用者线程中执行 AsyncTask(在单元测试的情况下应该没问题),您可以在当前线程中使用 Executor,如 https://stackoverflow.com/a/6583868/1266123
然后你在单元测试中运行 AsyncTask,如下所示
这仅适用于 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
And then you run your AsyncTask in your unit test like this
This is only working for HoneyComb and higher.
我为 Android 编写了足够多的unitests,只是想分享一下如何做到这一点。
首先,这里是负责等待和释放服务员的辅助类。没什么特别的:
SyncronizeTalker
接下来,让我们创建一个接口,其中包含一个方法,工作完成后应从
AsyncTask
调用该方法。当然,我们还想测试我们的结果:TestTaskItf
接下来让我们创建我们要测试的任务的一些框架:
最后 - 我们的unitest 类:
TestBuildGroupTask
就这样。
希望它会对某人有所帮助。
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
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
Next lets create some skeleton of our Task that we gonna test:
At last - our unitest class:
TestBuildGroupTask
That's all.
Hope it will help to someone.
如果您想测试
doInBackground
方法的结果,可以使用此方法。重写onPostExecute
方法并在那里执行测试。要等待 AsyncTask 完成,请使用 CountDownLatch。latch.await()
等待倒计时从 1(在初始化期间设置)到 0(由countdown()
方法完成)。This can be used if you want to test the result from the
doInBackground
method. Override theonPostExecute
method and perform the tests there. To wait for the AsyncTask to complete use CountDownLatch. Thelatch.await()
waits till the countdown runs from 1 (which is set during initialization) to 0 (which is done by thecountdown()
method).使用
join
怎么样?How about using
join
?使用这个简单的解决方案
Use this simple solution
大多数解决方案都需要为每个测试编写大量代码或更改类结构。如果您的项目中有很多正在测试的情况或很多 AsyncTasks,我发现它很难使用。
有一个库可以简化
AsyncTask
的测试过程。示例:基本上,它会运行 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:Basically, it runs your AsyncTask and test the result it returns after the
postComplete()
has been called.