매우 간단한 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 는 모드에서 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 Testing Framework 내에서 실행될 때 알림 메서드를 호출하지 않는다는 점을 제외하고는 정상적으로 작동합니다. 어떤 아이디어?
답변
일부 단위 테스트를 구현하는 동안 비슷한 문제가 발생했습니다. Executors와 함께 작동하는 일부 서비스를 테스트해야했고 서비스 콜백을 ApplicationTestCase 클래스의 테스트 메서드와 동기화해야했습니다. 일반적으로 테스트 메소드 자체는 콜백에 액세스하기 전에 완료되므로 콜백을 통해 전송 된 데이터는 테스트되지 않습니다. @UiThreadTest 흉상을 적용하려고 시도했지만 여전히 작동하지 않았습니다.
다음 방법을 찾았는데 효과가 있었지만 여전히 사용하고 있습니다. 나는 단순히 CountDownLatch 신호 객체를 사용하여 wait-notify (동기화 (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
}
답변
가까운 답을 많이 찾았지만 모든 부분을 올바르게 조합 한 사람은 없습니다. 따라서 이것은 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);
}
답변
이를 처리하는 방법은 다음에서 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());
}
기본적으로 junit은 기본 애플리케이션 UI가 아닌 별도의 스레드에서 테스트를 실행합니다. AsyncTask의 문서에 따르면 작업 인스턴스와 execute () 호출은 기본 UI 스레드에 있어야합니다. 이는 AsyncTask가 주 스레드 Looper
와 MessageQueue
내부 처리기가 제대로 작동하기 위해 의존하기 때문 입니다.
노트:
이전 @UiThreadTest
에 테스트 메서드에서 데코레이터로 사용 하여 테스트를 주 스레드에서 실행하도록 권장 했지만 테스트 메서드가 주 스레드에서 실행되는 동안 메시지가 처리되지 않기 때문에 AsyncTask 테스트에는 적합하지 않습니다. main MessageQueue — AsyncTask가 진행 상황에 대해 보내는 메시지를 포함하여 테스트가 중단됩니다.
답변
호출자 스레드에서 AsyncTask를 실행해도 괜찮다면 (단위 테스트의 경우 괜찮을 것임) https://stackoverflow.com/a/6583868/1266123에 설명 된대로 현재 스레드에서 Executor를 사용할 수 있습니다.
public class CurrentThreadExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
그런 다음 다음과 같이 단위 테스트에서 AsyncTask를 실행합니다.
myAsyncTask.executeOnExecutor(new CurrentThreadExecutor(), testParam);
이것은 HoneyComb 이상에서만 작동합니다.
답변
나는 안드로이드를위한 충분한 단위를 썼고 그 방법을 공유하고 싶습니다.
먼저 웨이터를 기다렸다가 풀어주는 헬퍼 클래스가 있습니다. 특별한 것은 없습니다.
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
}
다음으로 테스트 할 Task의 골격을 만들어 보겠습니다.
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)
}
}
그게 다야.
누군가에게 도움이되기를 바랍니다.
답변
doInBackground
메서드 의 결과를 테스트하려는 경우 사용할 수 있습니다 . onPostExecute
메서드를 재정의하고 거기에서 테스트를 수행합니다. AsyncTask가 완료 될 때까지 기다리려면 CountDownLatch를 사용하십시오. latch.await()
대기 (에 의해 이루어집니다 0 (초기화하는 동안 설정) (1)에서 카운트 다운이 실행 경작한다 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();
}
답변
이러한 솔루션의 대부분은 모든 테스트에 대해 작성하거나 클래스 구조를 변경하기 위해 많은 코드를 작성해야합니다. 테스트중인 상황이 많거나 프로젝트에 AsyncTask가 많은 경우 사용하기가 매우 어렵습니다.
테스트 과정을 용이하게 하는 라이브러리 가 있습니다 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()
호출 된 후 반환되는 결과를 테스트합니다 .