Android Test Framework를 사용한 Android AsyncTask 테스트


97

매우 간단한 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 내에서 실행될 때 알림 메서드를 호출하지 않는다는 점을 제외하고는 정상적으로 작동합니다. 어떤 아이디어?

답변:


125

일부 단위 테스트를 구현하는 동안 비슷한 문제가 발생했습니다. 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
}

1
무엇입니까 Service.doSomething()?
Peter Ajtai

11
AsynchTask를 테스트하고 있습니다. 이 작업을 수행했고, 백그라운드 작업은 호출되지 않는 것처럼 보이며 singnal은 영원히 기다립니다. (
Ixx

@Ixx, 당신은 전화 않았다 task.execute(Param...)전에 await()넣어 countDown()에서 onPostExecute(Result)? ( stackoverflow.com/a/5722193/253468 참조 ) 또한 @PeterAjtai Service.doSomethingtask.execute.
TWiStErRob 2013

정말 사랑스럽고 간단한 해결책입니다.
Maciej Beimcik

Service.doSomething()서비스 / 비동기 작업 호출을 대체해야하는 곳입니다. signal.countDown()구현해야하는 메서드 를 호출 해야합니다. 그렇지 않으면 테스트가 중단됩니다.
Victor R. Oliveira

94

가까운 답을 많이 찾았지만 모든 부분을 올바르게 조합 한 사람은 없습니다. 따라서 이것은 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);
}

1
완전한 예제를 작성 해주셔서 감사합니다. 저는 이것을 구현하는 데 많은 작은 문제가있었습니다.
Peter Ajtai

1 년이 조금 지나서 당신은 나를 구했습니다. Billy Brackeen 감사합니다!
MaTT 2012

8
시간 초과를 테스트 실패로 간주하려면 다음을 수행 할 수 있습니다.assertTrue(signal.await(...));
Jarett Millard 2013-08-27

4
Hey Billy이 구현을 시도했지만 runTestOnUiThread를 찾을 수 없습니다. 테스트 케이스가 AndroidTestCase를 확장해야합니까, 아니면 ActivityInstrumentationTestCase2를 확장해야합니까?
Doug Ray

3
@DougRay 동일한 문제가 발생했습니다. InstrumentationTestCase를 확장하면 runTestOnUiThread가 발견됩니다.
Luminaire

25

이를 처리하는 방법은 다음에서 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가 주 스레드 LooperMessageQueue내부 처리기가 제대로 작동하기 위해 의존하기 때문 입니다.

노트:

이전 @UiThreadTest에 테스트 메서드에서 데코레이터로 사용 하여 테스트를 주 스레드에서 실행하도록 권장 했지만 테스트 메서드가 주 스레드에서 실행되는 동안 메시지가 처리되지 않기 때문에 AsyncTask 테스트에는 적합하지 않습니다. main MessageQueue — AsyncTask가 진행 상황에 대해 보내는 메시지를 포함하여 테스트가 중단됩니다.


저를 구했습니다 ... 다른 스레드에서 "runTestOnUiThread"를 호출해야했지만 그렇지 않으면 "이 메서드는 주 응용 프로그램 스레드에서 호출 할 수 없습니다"라는 메시지가 표시됩니다
Matthieu

@Matthieu 데코레이터 runTestOnUiThread()와 함께 테스트 방법에서 사용 했습니까 @UiThreadTest? 작동하지 않습니다. 테스트 메서드에이없는 경우 @UiThreadTest기본적으로 기본이 아닌 자체 스레드에서 실행되어야합니다.
Alex Pretzlav 2012

1
그 대답은 순수한 보석입니다. 업데이트를 강조하기 위해 재 작업해야합니다. 정말 초기 답변을 유지하려면 배경 설명과 일반적인 함정으로 넣으십시오.
Snicolas

1
문서에 Deprecated in API level 24 developer.android.com/reference/android/test/…
Aivaras

1
더 이상 사용되지 않습니다. InstrumentationRegistry.getInstrumentation().runOnMainSync()대신 사용하세요!
Marco7757

5

호출자 스레드에서 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 이상에서만 작동합니다.


이 위로 가야한다
StefanTo

5

나는 안드로이드를위한 충분한 단위를 썼고 그 방법을 공유하고 싶습니다.

먼저 웨이터를 기다렸다가 풀어주는 헬퍼 클래스가 있습니다. 특별한 것은 없습니다.

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

그게 다야.

누군가에게 도움이되기를 바랍니다.


4

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

-1

이러한 솔루션의 대부분은 모든 테스트에 대해 작성하거나 클래스 구조를 변경하기 위해 많은 코드를 작성해야합니다. 테스트중인 상황이 많거나 프로젝트에 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()호출 된 후 반환되는 결과를 테스트합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.