Mam bardzo prosty przykład implementacji AsyncTask i mam problem z testowaniem go przy użyciu frameworka Android JUnit.
Działa dobrze, gdy tworzę instancję i wykonuję ją w normalnej aplikacji. Jednak gdy jest wykonywany z dowolnej klasy platformy Android Testing (tj. AndroidTestCase , ActivityUnitTestCase , ActivityInstrumentationTestCase2 itp.) Zachowuje się dziwnie:
- Wykonuje
doInBackground()
poprawnie metodę - Jednak to nie wywołuje żadnego z jej metody powiadomień (
onPostExecute()
,onProgressUpdate()
itp) - po prostu po cichu ignoruje je bez wykazywania jakichkolwiek błędów.
To jest bardzo prosty przykład 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");
}
}
To jest przypadek testowy. Tutaj AsyncTaskDemoActivity to bardzo proste działanie zapewniające interfejs użytkownika do testowania AsyncTask w trybie:
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());
}
}
Cały ten kod działa dobrze, z wyjątkiem faktu, że AsyncTask nie wywołuje swoich metod powiadamiania, gdy jest wykonywany w ramach Android Testing Framework. Jakieś pomysły?
Service.doSomething()
?task.execute(Param...)
wcześniejawait()
i umieścićcountDown()
wonPostExecute(Result)
? (patrz stackoverflow.com/a/5722193/253468 ) Również @PeterAjtaiService.doSomething
jest wywołaniem asynchronicznymtask.execute
.Service.doSomething()
to miejsce, w którym należy zamienić wywołanie zadania usługi / asynchronizacji. Pamiętaj, aby wywołaćsignal.countDown()
dowolną metodę, którą musisz zaimplementować, w przeciwnym razie test utknie.Znalazłem wiele bliskich odpowiedzi, ale żadna z nich nie ułożyła poprawnie wszystkich części razem. Jest to więc jedna poprawna implementacja podczas korzystania z android.os.AsyncTask w przypadkach testów JUnit.
źródło
assertTrue(signal.await(...));
Aby sobie z tym poradzić, należy uruchomić dowolny kod, który wywołuje AsyncTask w
runTestOnUiThread()
:Domyślnie junit uruchamia testy w osobnym wątku niż główny interfejs aplikacji. Dokumentacja AsyncTask mówi, że instancja zadania i wywołanie funkcji execute () muszą znajdować się w głównym wątku interfejsu użytkownika; Dzieje się tak, ponieważ AsyncTask zależy od głównego wątku
Looper
iMessageQueue
jego wewnętrznej obsługi, aby działała poprawnie.UWAGA:
Wcześniej zalecałem używanie
@UiThreadTest
jako dekoratora w metodzie testowej, aby wymusić uruchomienie testu w głównym wątku, ale nie jest to całkiem dobre w przypadku testowania AsyncTask, ponieważ podczas gdy Twoja metoda testowa działa w głównym wątku, żadne komunikaty nie są przetwarzane w main MessageQueue - w tym wiadomości, które AsyncTask wysyła o swoim postępie, powodując zawieszenie testu.źródło
runTestOnUiThread()
z metody testowej z@UiThreadTest
dekoratorem? To nie zadziała. Jeśli metoda testowa nie ma@UiThreadTest
, powinna domyślnie działać we własnym wątku innym niż główny.Deprecated in API level 24
developer.android.com/reference/android/test/…InstrumentationRegistry.getInstrumentation().runOnMainSync()
zamiast tego!Jeśli nie masz nic przeciwko wykonaniu AsyncTask w wątku wywołującego (powinno być w porządku w przypadku testów jednostkowych), możesz użyć Executora w bieżącym wątku, jak opisano w https://stackoverflow.com/a/6583868/1266123
Następnie uruchamiasz AsyncTask w teście jednostkowym w ten sposób
To działa tylko dla HoneyComb i nowszych.
źródło
Napisałem wystarczająco dużo testów jednostkowych dla Androida i po prostu chcę się podzielić, jak to zrobić.
Po pierwsze, tutaj jest klasa pomocnicza odpowiedzialna za czekanie i zwalnianie kelnera. Nic specjalnego:
SyncronizeTalker
Następnie stwórzmy interfejs z jedną metodą, która powinna być wywołana po
AsyncTask
zakończeniu pracy. Oczywiście, chcemy również przetestować nasze wyniki:TestTaskItf
Następnie stwórzmy szkielet naszego zadania, który będziemy testować:
Nareszcie - nasza najwyższa klasa:
TestBuildGroupTask
To wszystko.
Mam nadzieję, że komuś to pomoże.
źródło
Można tego użyć, jeśli chcesz przetestować wynik
doInBackground
metody. ZmieńonPostExecute
metodę i wykonaj tam testy. Aby poczekać na zakończenie AsyncTask, użyj CountDownLatch. Gdylatch.await()
czeka aż działa odliczanie od 1 (który jest ustawiany podczas inicjalizacji) 0 (która jest wykonywana przezcountdown()
metody).źródło
Większość z tych rozwiązań wymaga napisania dużej ilości kodu dla każdego testu lub zmiany struktury klasy. Które uważam za bardzo trudne w użyciu, jeśli masz wiele testowanych sytuacji lub wiele AsyncTasks w swoim projekcie.
Istnieje biblioteka, która ułatwia proces testowania
AsyncTask
. Przykład:Zasadniczo uruchamia AsyncTask i testuje wynik, który zwraca po
postComplete()
wywołaniu.źródło