studio programacion herramientas fundamentos con avanzado aplicaciones android unit-testing android-fragments fragment activityunittestcase

programacion - manual de android en pdf



Unidad prueba un fragmento de Android (4)

Agregando a la respuesta de @abhijit.mitkar.

Dado un escenario que su fragmento no es un miembro público en la actividad bajo prueba.

protected void setUp() { mActivity = getActivity(); mFragment = new TheTargetFragment(); FragmentTransaction transaction = mActivity.getSupportFragmentManager().beginTransaction(); transaction.add(R.id.fragment_container, mFragment, "FRAGMENT_TAG"); transaction.commit(); }

El propósito del código anterior es reemplazar el fragmento con un nuevo objeto de fragmento al que tenemos acceso.

El siguiente código le permitirá obtener acceso a los fragmentos de los miembros de la interfaz de usuario.

TextView randomTextView= (TextView) mFragment.getView().findViewById(R.id.textViewRandom);

Obtener la UI de la actividad no le dará el resultado esperado.

TextView randomTextView= (TextView) mActivity.findViewById(R.id.textViewRandom);

Finalmente, si desea hacer algunos cambios en la interfaz de usuario. Como un buen desarrollador de Android, hazlo en el hilo principal.

mActivity.runOnUiThread(new Runnable() { @Override public void run() { // set text view''s value } });

Nota: Es posible que desee darle un Thread.sleep () cada vez que finalice una prueba. Para evitar el bloqueo, getInstrumentation (). WaitForIdleSync (); no parece funcionar siempre.

Utilicé ActivityInstrumentationTestCase2 porque estaba haciendo pruebas funcionales.

Quiero probar la unidad de una clase de Android Fragment.

¿Puedo configurar una prueba usando AndroidTestCase o necesito usar ApplicationTestCase?

¿Hay algún ejemplo útil de cómo se pueden utilizar estos dos TestCases? Los ejemplos de prueba en el sitio del desarrollador son mínimos y solo parecen enfocarse en probar Actividades.

Todo lo que he encontrado en otros lugares son ejemplos en los que se amplía la clase AndroidTestCase, pero luego todo lo que se prueba es agregar dos números o si se usa el contexto, simplemente hace un simple obtener y prueba que algo no es nulo.

Según lo entiendo, un Fragmento tiene que vivir dentro de una Actividad. Entonces, ¿podría crear una Actividad de simulacro u obtener la Aplicación o el Contexto para proporcionar una Actividad dentro de la cual pueda probar mi Fragmento?

¿Necesito crear mi propia Actividad y luego usar ActivityUnitTestCase?

Gracias por tu ayuda.

Trev


Estaba luchando con la misma pregunta. Especialmente, ya que la mayoría de los ejemplos de código ya están desactualizados + Android Studio / SDK está mejorando, por lo que las respuestas antiguas a veces ya no son relevantes.

Entonces, lo primero es lo primero: debe determinar si desea usar pruebas instrumentales o simples JUnit .

La diferencia entre ellos bellamente descrita por SD here ; En resumen: las pruebas JUnit son más livianas y no requieren un emulador para ejecutarse, Instrumental: le brindan la experiencia más cercana posible al dispositivo real (sensores, gps, interacción con otras aplicaciones, etc.). Lea también más sobre pruebas en Android .

1. JUnit prueba de fragmentos

Digamos que no necesitas pruebas instrumentales pesadas y pruebas simples de junit son suficientes. Yo uso un buen framework Robolectric para este propósito.

En gradle agregar:

dependencies { ..... testCompile ''junit:junit:4.12'' testCompile ''org.robolectric:robolectric:3.0'' testCompile "org.mockito:mockito-core:1.10.8" testCompile (''com.squareup.assertj:assertj-android:1.0.0'') { exclude module: ''support-annotations'' } ..... }

Mockito, AsserJ son opcionales, pero los encontré muy útiles, así que recomiendo incluirlos también.

Luego, en Variantes de compilación, especifique las Pruebas unitarias como artefacto de prueba :

Ahora es el momento de escribir algunas pruebas reales :-) Como ejemplo, tomemos el proyecto de ejemplo estándar "Actividad en blanco con fragmento".

Agregué algunas líneas de código, para tener algo que probar:

import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import java.util.List; public class MainActivityFragment extends Fragment { private List<Cow> cows; public MainActivityFragment() {} @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { cows = new ArrayList<>(); cows.add(new Cow("Burka", 10)); cows.add(new Cow("Zorka", 9)); cows.add(new Cow("Kruzenshtern", 15)); return inflater.inflate(R.layout.fragment_main, container, false); } int calculateYoungCows(int maxAge) { if (cows == null) { throw new IllegalStateException("onCreateView hasn''t been called"); } if (getActivity() == null) { throw new IllegalStateException("Activity is null"); } if (getView() == null) { throw new IllegalStateException("View is null"); } int result = 0; for (Cow cow : cows) { if (cow.age <= maxAge) { result++; } } return result; } }

Y clase vaca:

public class Cow { public String name; public int age; public Cow(String name, int age) { this.name = name; this.age = age; } }

El conjunto de pruebas de Robolectic sería algo así como:

import android.app.Application; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.test.ApplicationTestCase; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.annotation.Config; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk=21) public class MainActivityFragmentTest extends ApplicationTestCase<Application> { public MainActivityFragmentTest() { super(Application.class); } MainActivity mainActivity; MainActivityFragment mainActivityFragment; @Before public void setUp() { mainActivity = Robolectric.setupActivity(MainActivity.class); mainActivityFragment = new MainActivityFragment(); startFragment(mainActivityFragment); } @Test public void testMainActivity() { Assert.assertNotNull(mainActivity); } @Test public void testCowsCounter() { assertThat(mainActivityFragment.calculateYoungCows(10)).isEqualTo(2); assertThat(mainActivityFragment.calculateYoungCows(99)).isEqualTo(3); } private void startFragment( Fragment fragment ) { FragmentManager fragmentManager = mainActivity.getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(fragment, null ); fragmentTransaction.commit(); } }

Es decir, creamos actividad a través de Robolectric.setupActivity , nuevo fragmento en las clases de prueba ''setUp (). Opcionalmente, puede iniciar inmediatamente el fragmento desde setUp () o puede hacerlo directamente desde la prueba.

¡NÓTESE BIEN! No he dedicado demasiado tiempo a esto, pero parece que es casi imposible vincularlo con Dagger (no sé si es más fácil con Dagger2), ya que no puedes establecer aplicaciones de prueba personalizadas con inyecciones falsas.

2. Prueba instrumental de fragmentos

La complejidad de este enfoque depende en gran medida de si está utilizando la inyección Dagger / Dependency en la aplicación que desea probar.

En Variantes de compilación, especifique Android Instrumental Tests como Test Artifact :

En Gradle agrego estas dependencias:

dependencies { ..... androidTestCompile "com.google.dexmaker:dexmaker:1.1" androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.1" androidTestCompile ''com.squareup.assertj:assertj-android:1.0.0'' androidTestCompile "org.mockito:mockito-core:1.10.8" } ..... }

(una vez más, casi todos son opcionales, pero pueden hacer su vida mucho más fácil)

- Si no tienes Daga

Este es un camino feliz. La diferencia con Robolectric de lo anterior sería solo en pequeños detalles.

Paso previo 1 : si vas a usar Mockito, debes habilitarlo para que se ejecute en los dispositivos y emuladores con este truco:

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

El MainActivityFragment se mantiene igual, como arriba. Entonces el conjunto de prueba se vería así:

package com.klogi.myapplication; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.test.ActivityInstrumentationTestCase2; import junit.framework.Assert; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class MainActivityFragmentTest extends ActivityInstrumentationTestCase2<MainActivity> { public MainActivityFragmentTest() { super(MainActivity.class); } MainActivity mainActivity; MainActivityFragment mainActivityFragment; @Override protected void setUp() throws Exception { TestUtils.enableMockitoOnDevicesAndEmulators(); mainActivity = getActivity(); mainActivityFragment = new MainActivityFragment(); } public void testMainActivity() { Assert.assertNotNull(mainActivity); } public void testCowsCounter() { startFragment(mainActivityFragment); assertThat(mainActivityFragment.calculateYoungCows(10)).isEqualTo(2); assertThat(mainActivityFragment.calculateYoungCows(99)).isEqualTo(3); } private void startFragment( Fragment fragment ) { FragmentManager fragmentManager = mainActivity.getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(fragment, null); fragmentTransaction.commit(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { getActivity().getSupportFragmentManager().executePendingTransactions(); } }); getInstrumentation().waitForIdleSync(); } }

Como puede ver, la clase de prueba es una extensión de la clase ActivityInstrumentationTestCase2 . Además, es muy importante prestar atención al método startFragment , que ha cambiado comparándolo con el ejemplo de JUnit: de manera predeterminada, las pruebas no se están ejecutando en el subproceso de la interfaz de usuario y necesitamos solicitar explícitamente la ejecución pendiente de las transacciones de FragmentManager.

- Si tienes Daga

Las cosas se ponen serias aquí :-)

En primer lugar, nos estamos deshaciendo de ActivityInstrumentationTestCase2 en favor de la clase ActivityUnitTestCase , como una clase base para todas las clases de prueba de fragmentos.

Como de costumbre, no es tan simple y hay varias trampas ( this es uno de los ejemplos). Entonces, debemos utilizar nuestro AcitivityUnitTestCase para ActivityUnitTestCaseOverride

Es demasiado tiempo para publicarlo completamente aquí, así que cargo una versión completa de él a 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; } ....... }

Cree un AbstractFragmentTest abstracto para todas sus pruebas de fragmentos:

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

Hay varias cosas importantes aquí.

1) Anulamos el método setActivity () para establecer el tema de AppCompact para la actividad. Sin eso, el traje de prueba se bloqueará.

2 ) método setUpActivityAndFragment ():

I. crea actividad (=> getActivity () comienza a devolver valor no nulo, en pruebas y en la aplicación que está bajo prueba) 1) onCreate () de actividad llamada;

2) onStart () de actividad llamada;

3) onResume () de actividad llamada;

II. adjuntar y comienza fragmento a la actividad

1) onAttach () del fragmento llamado;

2) onCreateView () del fragmento llamado;

3) onStart () del fragmento llamado;

4) onResume () del fragmento llamado;

3) método createMockApplication (): al igual que en la versión sin daga, en el paso previo 1, habilitamos la burla en los dispositivos y en los emuladores.

Luego, reemplazamos la aplicación normal con sus inyecciones con nuestra aplicación TestApplication personalizada.

MockInjectionRegistration se ve así:

.... import javax.inject.Singleton; import dagger.Module; import dagger.Provides; import de.greenrobot.event.EventBus; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @Module( injects = { .... MainActivity.class, MyWorkFragment.class, HomeFragment.class, ProfileFragment.class, .... }, addsTo = DelveMobileInjectionRegistration.class, overrides = true ) public final class MockInjectionRegistration { ..... public DataSource dataSource; public EventBus eventBus; public MixpanelAPI mixpanel; ..... public MockInjectionRegistration() { ..... dataSource = mock(DataSource.class); eventBus = mock(EventBus.class); mixpanel = mock(MixpanelAPI.class); MixpanelAPI.People mixpanelPeople = mock(MixpanelAPI.People.class); when(mixpanel.getPeople()).thenReturn(mixpanelPeople); ..... } ........... @Provides @Singleton @SuppressWarnings("unused") // invoked by Dagger DataSource provideDataSource() { Guard.valueIsNotNull(dataSource); return dataSource; } @Provides @Singleton @SuppressWarnings("unused") // invoked by Dagger EventBus provideEventBus() { Guard.valueIsNotNull(eventBus); return eventBus; } @Provides @Singleton @SuppressWarnings("unused") // invoked by Dagger MixpanelAPI provideMixpanelAPI() { Guard.valueIsNotNull(mixpanel); return mixpanel; } ......... }

Es decir, en lugar de clases reales, proporcionamos a los fragmentos sus versiones falsas. (Que son fácilmente rastreables, permite configurar resultados de llamadas a métodos, etc.).

Y TestApplication es solo su extensión personalizada de Application, que debería ser compatible con los módulos de configuración e inicializar el ObjectGraph.

Estos fueron los pasos previos para comenzar a escribir las pruebas :) Ahora la parte simple, las pruebas reales:

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

¡Eso es! Ahora tiene habilitadas las pruebas de Instrumental / JUnit para sus Fragmentos.

Sinceramente espero que esta publicación ayude a alguien.


Estoy bastante seguro de que puedes hacer lo que dices, crear una actividad simulada y probar el fragmento desde allí. Simplemente debe exportar la biblioteca de compatibilidad en el proyecto principal y podrá acceder a los fragmentos del proyecto de prueba. Voy a crear un proyecto de muestra y probar el código aquí y actualizaré mi respuesta en función de lo que descubra.

Para obtener más información sobre cómo exportar la biblioteca de compatibilidad, consulte here .


Supongamos que tiene una clase FragmentActivity llamada ''MyFragmentActivity'' en la que se agrega una clase Fragment pública llamada ''MyFragment'' utilizando FragmentTransaction. Simplemente cree una clase ''JUnit Test Case'' que amplíe ActivityInstrumentationTestCase2 en su proyecto de prueba. Luego simplemente llame a getActivity () y acceda al objeto MyFragment y a sus miembros públicos para escribir casos de prueba.

Consulte el fragmento de código a continuación:

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

Espero que esto ayude. Acepte mi respuesta si lo encuentra útil. Gracias.