So stellen Sie den Unit-Test in Android auf Fragment ein
Lesezeit: 14 Minuten
Trev Sorbie
Ich möchte eine Android-Fragment-Klasse komponententesten.
Kann ich einen Test mit AndroidTestCase einrichten oder muss ich ApplicationTestCase verwenden?
Gibt es nützliche Beispiele, wie diese beiden Testfälle verwendet werden können? Die Testbeispiele auf der Entwicklerseite sind minimal und scheinen sich nur auf das Testen von Aktivitäten zu konzentrieren.
Alles, was ich an anderer Stelle gefunden habe, sind Beispiele, bei denen die AndroidTestCase-Klasse erweitert wird, aber dann alles, was getestet wird, das Addieren von zwei Zahlen ist, oder wenn der Kontext verwendet wird, macht es einfach ein einfaches Get und testet, dass etwas nicht null ist!
So wie ich es verstehe, muss ein Fragment innerhalb einer Aktivität leben. Könnte ich also eine Scheinaktivität erstellen oder die Anwendung oder den Kontext dazu bringen, eine Aktivität bereitzustellen, in der ich mein Fragment testen kann?
Muss ich meine eigene Aktivität erstellen und dann ActivityUnitTestCase verwenden?
Konstantin Loginow
Ich hatte mit der gleichen Frage zu kämpfen. Vor allem, da die meisten Codebeispiele bereits veraltet sind und Android Studio/SDKs verbessert werden, sind alte Antworten manchmal nicht mehr relevant.
Also, das Wichtigste zuerst: Sie müssen bestimmen, ob Sie verwenden möchten Instrumental oder einfach JUnit Prüfungen.
Der Unterschied zwischen ihnen wurde hier von SD wunderschön beschrieben; Kurz gesagt: JUnit-Tests sind leichter und erfordern keinen Emulator, um ausgeführt zu werden. Instrumental – geben Ihnen die Erfahrung, die dem tatsächlichen Gerät am nächsten kommt (Sensoren, GPS, Interaktion mit anderen Apps usw.). Lesen Sie auch mehr über Testen in Android.
1. JUnit-Test von Fragmenten
Nehmen wir an, Sie brauchen keine schweren Instrumentaltests und einfache Junit-Tests reichen aus. Ich verwende einen schönen Rahmen Roboterelektrik für diesen Zweck.
Dh wir erstellen Aktivität über Robolectric.setupActivity, neues Fragment im setUp() der Testklassen. Optional können Sie das Fragment sofort aus setUp() starten oder direkt aus dem Test heraus.
Achtung! Ich habe nicht ausgegeben zu viel Zeit darauf, aber es sieht so aus, als wäre es fast unmöglich, es mit Dagger zusammenzubinden (ich weiß nicht, ob es mit Dagger2 einfacher ist), da Sie keine benutzerdefinierte Testanwendung mit verspotteten Injektionen festlegen können.
2. Instrumentelle Prüfung von Fragmenten
Die Komplexität dieses Ansatzes hängt stark davon ab, ob Sie Dagger/Dependency Injection in der App verwenden, die Sie testen möchten.
Im Varianten bauen angeben Android-Instrumentaltests Als ein Artefakt testen:
(Auch hier sind so ziemlich alle optional, aber sie können Ihr Leben so viel einfacher machen)
– Wenn Sie keinen Dolch haben
Dies ist ein glücklicher Weg. Der Unterschied zu Robolectric von oben wäre nur in kleinen Details.
Vorstufe 1: Wenn Sie Mockito verwenden möchten, müssen Sie es mit diesem Hack aktivieren, damit es auf den Geräten und Emulatoren ausgeführt werden kann:
public class TestUtils {
private static final String CACHE_DIRECTORY = "/data/data/" + BuildConfig.APPLICATION_ID + "/cache";
public static final String DEXMAKER_CACHE_PROPERTY = "dexmaker.dexcache";
public static void enableMockitoOnDevicesAndEmulators() {
if (System.getProperty(DEXMAKER_CACHE_PROPERTY) == null || System.getProperty(DEXMAKER_CACHE_PROPERTY).isEmpty()) {
File file = new File(CACHE_DIRECTORY);
if (!file.exists()) {
final boolean success = file.mkdirs();
if (!success) {
fail("Unable to create cache directory required for Mockito");
}
}
System.setProperty(DEXMAKER_CACHE_PROPERTY, file.getPath());
}
}
}
Das MainActivityFragment bleibt wie oben gleich. Das Testset würde also so aussehen:
Wie Sie sehen können, ist die Test-Klasse eine Erweiterung von ActivityInstrumentationTestCase2 Klasse. Außerdem ist es sehr wichtig, darauf zu achten startFragment -Methode, die sich im Vergleich zum JUnit-Beispiel geändert hat: Standardmäßig werden keine Tests im UI-Thread ausgeführt, und wir müssen die Ausführung explizit aufrufen, wenn die Transaktionen von FragmentManager anstehen.
– Wenn Sie Dolch haben
Hier wird es ernst 🙂
Zuerst werden wir los ActivityInstrumentationTestCase2 zugunsten ActivityUnitTestCase Klasse als Basisklasse für alle Testklassen des Fragments.
Wie üblich ist es nicht so einfach und es gibt mehrere Fallstricke (dies ist eines von Beispielen). Also müssen wir unsere pimpen ActivityUnitTestCase zu ActivityUnitTestCaseOverride
Es ist ein bisschen zu lang, um es hier vollständig zu posten, also lade ich die vollständige Version davon hoch github;
public abstract class ActivityUnitTestCaseOverride<T extends Activity>
extends ActivityUnitTestCase<T> {
........
private Class<T> mActivityClass;
private Context mActivityContext;
private Application mApplication;
private MockParent mMockParent;
private boolean mAttached = false;
private boolean mCreated = false;
public ActivityUnitTestCaseOverride(Class<T> activityClass) {
super(activityClass);
mActivityClass = activityClass;
}
@Override
public T getActivity() {
return (T) super.getActivity();
}
@Override
protected void setUp() throws Exception {
super.setUp();
// default value for target context, as a default
mActivityContext = getInstrumentation().getTargetContext();
}
/**
* Start the activity under test, in the same way as if it was started by
* {@link android.content.Context#startActivity Context.startActivity()}, providing the
* arguments it supplied. When you use this method to start the activity, it will automatically
* be stopped by {@link #tearDown}.
* <p/>
* <p>This method will call onCreate(), but if you wish to further exercise Activity life
* cycle methods, you must call them yourself from your test case.
* <p/>
* <p><i>Do not call from your setUp() method. You must call this method from each of your
* test methods.</i>
*
* @param intent The Intent as if supplied to {@link android.content.Context#startActivity}.
* @param savedInstanceState The instance state, if you are simulating this part of the life
* cycle. Typically null.
* @param lastNonConfigurationInstance This Object will be available to the
* Activity if it calls {@link android.app.Activity#getLastNonConfigurationInstance()}.
* Typically null.
* @return Returns the Activity that was created
*/
protected T startActivity(Intent intent, Bundle savedInstanceState,
Object lastNonConfigurationInstance) {
assertFalse("Activity already created", mCreated);
if (!mAttached) {
assertNotNull(mActivityClass);
setActivity(null);
T newActivity = null;
try {
IBinder token = null;
if (mApplication == null) {
setApplication(new MockApplication());
}
ComponentName cn = new ComponentName(getInstrumentation().getTargetContext(), mActivityClass.getName());
intent.setComponent(cn);
ActivityInfo info = new ActivityInfo();
CharSequence title = mActivityClass.getName();
mMockParent = new MockParent();
String id = null;
newActivity = (T) getInstrumentation().newActivity(mActivityClass, mActivityContext,
token, mApplication, intent, info, title, mMockParent, id,
lastNonConfigurationInstance);
} catch (Exception e) {
assertNotNull(newActivity);
}
assertNotNull(newActivity);
setActivity(newActivity);
mAttached = true;
}
T result = getActivity();
if (result != null) {
getInstrumentation().callActivityOnCreate(getActivity(), savedInstanceState);
mCreated = true;
}
return result;
}
protected Class<T> getActivityClass() {
return mActivityClass;
}
@Override
protected void tearDown() throws Exception {
setActivity(null);
// Scrub out members - protects against memory leaks in the case where someone
// creates a non-static inner class (thus referencing the test case) and gives it to
// someone else to hold onto
scrubClass(ActivityInstrumentationTestCase.class);
super.tearDown();
}
/**
* Set the application for use during the test. You must call this function before calling
* {@link #startActivity}. If your test does not call this method,
*
* @param application The Application object that will be injected into the Activity under test.
*/
public void setApplication(Application application) {
mApplication = application;
}
.......
}
Erstellen Sie einen abstrakten AbstractFragmentTest für alle Ihre Fragmenttests:
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
/**
* Common base class for {@link Fragment} tests.
*/
public abstract class AbstractFragmentTest<TFragment extends Fragment, TActivity extends FragmentActivity> extends ActivityUnitTestCaseOverride<TActivity> {
private TFragment fragment;
protected MockInjectionRegistration mocks;
protected AbstractFragmentTest(TFragment fragment, Class<TActivity> activityType) {
super(activityType);
this.fragment = parameterIsNotNull(fragment);
}
@Override
protected void setActivity(Activity testActivity) {
if (testActivity != null) {
testActivity.setTheme(R.style.AppCompatTheme);
}
super.setActivity(testActivity);
}
/**
* Get the {@link Fragment} under test.
*/
protected TFragment getFragment() {
return fragment;
}
protected void setUpActivityAndFragment() {
createMockApplication();
final Intent intent = new Intent(getInstrumentation().getTargetContext(),
getActivityClass());
startActivity(intent, null, null);
startFragment(getFragment());
getInstrumentation().callActivityOnStart(getActivity());
getInstrumentation().callActivityOnResume(getActivity());
}
private void createMockApplication() {
TestUtils.enableMockitoOnDevicesAndEmulators();
mocks = new MockInjectionRegistration();
TestApplication testApplication = new TestApplication(getInstrumentation().getTargetContext());
testApplication.setModules(mocks);
testApplication.onCreate();
setApplication(testApplication);
}
private void startFragment(Fragment fragment) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
}
Hier gibt es mehrere wichtige Dinge.
1) Wir überschreiben setActivity() -Methode, um das AppCompact-Design auf die Aktivität festzulegen. Ohne das wird der Testanzug abstürzen.
2) setUpActivityAndFragment()-Methode:
ICH. erstellt Aktivität ( => getActivity() beginnt mit der Rückgabe eines Nicht-Null-Werts, in Tests und in der zu testenden App)
onCreate() der aufgerufenen Aktivität;
onStart() der aufgerufenen Aktivität;
onResume() der aufgerufenen Aktivität;
II. Fragment an die Aktivität anhängen und starten
onAttach() des aufgerufenen Fragments;
onCreateView() des aufgerufenen Fragments;
onStart() des aufgerufenen Fragments;
onResume() des aufgerufenen Fragments;
3) createMockApplication()-Methode: Wie in der Non-Dagger-Version aktivieren wir in Pre-Step 1 das Mocking auf den Geräten und auf den Emulatoren.
Dann ersetzen wir die normale Anwendung mit ihren Injektionen durch unsere individuelle Testanwendung!
Dh statt echter Klassen stellen wir den Fragmenten ihre verspotteten Versionen zur Verfügung. (Die leicht nachvollziehbar sind, erlaubt es, Ergebnisse von Methodenaufrufen zu konfigurieren usw.).
Und die TestApplication ist nur Ihre benutzerdefinierte Erweiterung von Application, die das Setzen von Modulen unterstützen und den ObjectGraph initialisieren sollte.
Dies waren Vorstufen, um mit dem Schreiben der Tests zu beginnen 🙂
Jetzt der einfache Teil, die eigentlichen Tests:
public class SearchFragmentTest extends AbstractFragmentTest<SearchFragment, MainActivity> {
public SearchFragmentTest() {
super(new SearchFragment(), MainActivity.class);
}
@UiThreadTest
public void testOnCreateView() throws Exception {
setUpActivityAndFragment();
SearchFragment searchFragment = getFragment();
assertNotNull(searchFragment.adapter);
assertNotNull(SearchFragment.getSearchAdapter());
assertNotNull(SearchFragment.getSearchSignalLogger());
}
@UiThreadTest
public void testOnPause() throws Exception {
setUpActivityAndFragment();
SearchFragment searchFragment = getFragment();
assertTrue(Strings.isNullOrEmpty(SharedPreferencesTools.getString(getActivity(), SearchFragment.SEARCH_STATE_BUNDLE_ARGUMENT)));
searchFragment.searchBoxRef.setCurrentConstraint("abs");
searchFragment.onPause();
assertEquals(searchFragment.searchBoxRef.getCurrentConstraint(), SharedPreferencesTools.getString(getActivity(), SearchFragment.SEARCH_STATE_BUNDLE_ARGUMENT));
}
@UiThreadTest
public void testOnQueryTextChange() throws Exception {
setUpActivityAndFragment();
reset(mocks.eventBus);
getFragment().onQueryTextChange("Donald");
Thread.sleep(300);
// Should be one cached, one uncached event
verify(mocks.eventBus, times(2)).post(isA(SearchRequest.class));
verify(mocks.eventBus).post(isA(SearchLoadingIndicatorEvent.class));
}
@UiThreadTest
public void testOnQueryUpdateEventWithDifferentConstraint() throws Exception {
setUpActivityAndFragment();
reset(mocks.eventBus);
getFragment().onEventMainThread(new SearchResponse(new ArrayList<>(), "Donald", false));
verifyNoMoreInteractions(mocks.eventBus);
}
....
}
Das ist es!
Jetzt haben Sie Instrumental/JUnit-Tests für Ihre Fragmente aktiviert.
Ich hoffe aufrichtig, dass dieser Beitrag jemandem hilft.
Das ist etwas wirklich Gutes. Vielen Dank, dass Sie mit uns geteilt haben!
– Vulovic Vukasin
6. Juli 2016 um 8:21 Uhr
Einfacher wäre es zu extrahieren calculateYoungCows() -Methode in eine separate Klasse und testen Sie diese einfach.
– Andy Res
19. August 2016 um 18:42 Uhr
abhijit.mitkar
Angenommen, Sie haben eine FragmentActivity-Klasse mit dem Namen „MyFragmentActivity“, in der eine öffentliche Fragment-Klasse mit dem Namen „MyFragment“ mithilfe von FragmentTransaction hinzugefügt wird. Erstellen Sie einfach eine „JUnit Test Case“-Klasse, die ActivityInstrumentationTestCase2 in Ihrem Testprojekt erweitert. Rufen Sie dann einfach getActivity() auf und greifen Sie auf das MyFragment-Objekt und seine öffentlichen Member zu, um Testfälle zu schreiben.
Siehe das Code-Snippet unten:
// TARGET CLASS
public class MyFragmentActivity extends FragmentActivity {
public MyFragment myFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
myFragment = new MyFragment();
fragmentTransaction.add(R.id.mainFragmentContainer, myFragment);
fragmentTransaction.commit();
}
}
// TEST CLASS
public class MyFragmentActivityTest extends android.test.ActivityInstrumentationTestCase2<MyFragmentActivity> {
MyFragmentActivity myFragmentActivity;
MyFragment myFragment;
public MyFragmentActivityTest() {
super(MyFragmentActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFragmentActivity = (MyFragmentActivity) getActivity();
myFragment = myFragmentActivity.myFragment;
}
public void testPreConditions() {
assertNotNull(myFragmentActivity);
assertNotNull(myFragment);
}
public void testAnythingFromMyFragment() {
// access any public members of myFragment to test
}
}
Ich hoffe das hilft. Akzeptieren Sie meine Antwort, wenn Sie dies nützlich finden. Vielen Dank.
Wie umgehen Sie TestRunner(16162): java.lang.RuntimeException: Aktivität kann nicht aufgelöst werden für: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=com.example/.test.MyFragmentActivityTest $MyFragmentActivity }
– Mach
19. Juli 2013 um 11:12 Uhr
@mach Kannst du bitte den vollständigen Stacktrace bereitstellen?
– abhijit.mitkar
23. Juli 2013 um 10:03 Uhr
Das obige Beispiel ist kein Komponententest, sondern ein Instrumentierungstest.
– Muhammad Babar
5. Juni 2015 um 11:04 Uhr
Ich bin mir ziemlich sicher, dass Sie tun können, was Sie sagen, eine Scheinaktivität erstellen und das Fragment von dort aus testen. Sie müssen nur die Kompatibilitätsbibliothek in das Hauptprojekt exportieren und können auf die Fragmente aus dem Testprojekt zugreifen. Ich werde ein Beispielprojekt erstellen und den Code hier testen und meine Antwort basierend auf dem, was ich herausfinde, aktualisieren.
Weitere Informationen zum Exportieren der Kompatibilitätsbibliothek finden Sie unter hier.
Können Sie hier etwas Code teilen, um Fragmente zu testen. Ich habe Probleme beim Unit-Testing von Fragmenten!
– Muhammad Babar
5. Juni 2015 um 11:16 Uhr
Christoph Cutas
Hinzufügen zu @abhijit.mitkars Antwort.
In einem Szenario, in dem Ihr Fragment kein öffentliches Mitglied in der getesteten Aktivität ist.
Schließlich, wenn Sie einige Änderungen an der Benutzeroberfläche vornehmen möchten. Machen Sie es wie ein guter Android-Entwickler im Hauptthread.
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
// set text view's value
}
});
Notiz:
Vielleicht möchten Sie ihm jedes Mal ein Thread.sleep() geben, wenn ein Test endet. Um ein Abstürzen zu vermeiden, muss getInstrumentation().waitForIdleSync(); scheint nicht immer zu funktionieren.
ich benutzte ActivityInstrumentationTestCase2 seit ich Funktionstests mache.
14360200cookie-checkSo stellen Sie den Unit-Test in Android auf Fragment einyes