Android
Test de l'interface utilisateur avec Espresso
Recherche…
Remarques
Expresso
La feuille de triche expresso vous aidera à écrire vos tests et ce que vous voulez tester:
https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/
La documentation officielle est toujours un bon endroit pour référence:
https://google.github.io/android-testing-support-library/docs/espresso/index.html
Suggestions avancées de vidéos expresso par Google: https://www.youtube.com/watch?v=isihPOY2vS4
Dépannage
- Lorsque vous essayez de faire défiler, veillez à fermer le clavier en premier:
Watchout: ne pas utiliser la version "Espresso" ne fera rien en dehors de ViewAction. Cela peut ne pas être évident si vous avez une importation sur la version de ViewAction, car ils ont exactement le même nom de méthode.
ViewActions.closeSoftKeyboard;
Espresso.closeSoftKeyboard();
- Lorsque vous exécutez des tests ensemble dans une suite plutôt qu'individuellement, sachez que l'activité du test précédent est peut-être toujours en cours d'exécution. Ne vous fiez pas à l'appel du test précédent onDestroy () avant les tests en cours onResume (). Il s'avère que c'est en fait un bogue : http://b.android.com/201513
Configurer Espresso
Dans le fichier build.gradle
de votre module d'application Android, ajoutez les prochaines dépendances:
dependencies {
// Android JUnit Runner
androidTestCompile 'com.android.support.test:runner:0.5'
// JUnit4 Rules
androidTestCompile 'com.android.support.test:rules:0.5'
// Espresso core
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2'
//UI Automator tests
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.2.2'
}
Spécifiez AndroidJUnitRunner
pour le paramètre testInstrumentationRunner
dans le fichier build.gradle
.
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
En outre, ajoutez cette dépendance pour fournir une prise en charge intentionnelle
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
Et ajoutez celui-ci pour la prise en charge des tests WebView
// Espresso-web for WebView support
androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
Créer une classe de test expresso
Placez la classe java suivante dans src / androidTest / java et lancez-la.
public class UITest {
@Test public void Simple_Test() {
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
}
}
Ouvrir Fermer DrawerLayout
public final class DrawerLayoutTest {
@Test public void Open_Close_Drawer_Layout() {
onView(withId(R.id.drawer_layout)).perform(actionOpenDrawer());
onView(withId(R.id.drawer_layout)).perform(actionCloseDrawer());
}
public static ViewAction actionOpenDrawer() {
return new ViewAction() {
@Override public Matcher<View> getConstraints() {
return isAssignableFrom(DrawerLayout.class);
}
@Override public String getDescription() {
return "open drawer";
}
@Override public void perform(UiController uiController, View view) {
((DrawerLayout) view).openDrawer(GravityCompat.START);
}
};
}
public static ViewAction actionCloseDrawer() {
return new ViewAction() {
@Override public Matcher<View> getConstraints() {
return isAssignableFrom(DrawerLayout.class);
}
@Override public String getDescription() {
return "close drawer";
}
@Override public void perform(UiController uiController, View view) {
((DrawerLayout) view).closeDrawer(GravityCompat.START);
}
};
}
}
Test de l'interface utilisateur simple expresso
Outils de test de l'interface utilisateur
Appium et Espresso sont les deux principaux outils actuellement utilisés pour les tests d’interface utilisateur.
Appium | Expresso |
---|---|
test de blackbox | test de boîte blanche / grise |
ce que vous voyez est ce que vous pouvez tester | peut modifier le fonctionnement interne de l'application et le préparer pour le test, par exemple enregistrer certaines données dans la base de données ou les préférences partagées avant de lancer le test |
Utilisé principalement pour des tests d'intégration de bout en bout et des flux d'utilisateurs entiers | tester la fonctionnalité d'un écran et / ou d'un flux |
peut être abstraite afin que le test écrit puisse être exécuté sur iOS et Android | Android uniquement |
Bien soutenu | Bien soutenu |
prend en charge les tests parallèles sur plusieurs périphériques avec grille de sélénium | Pas de tests parallèles prêts à l'emploi, des plug-ins comme Spoon existent jusqu'à ce que le véritable support de Google apparaisse |
Comment ajouter du café au projet
dependencies {
// Set this dependency so you can use Android JUnit Runner
androidTestCompile 'com.android.support.test:runner:0.5'
// Set this dependency to use JUnit 4 rules
androidTestCompile 'com.android.support.test:rules:0.5'
// Set this dependency to build and run Espresso tests
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
// Set this dependency to build and run UI Automator tests
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.2.2'
}
REMARQUE Si vous utilisez les dernières bibliothèques de support, les annotations, etc., vous devez exclure les anciennes versions de l'espresso pour éviter les collisions:
// there is a conflict with the test support library (see http://stackoverflow.com/questions/29857695)
// so for now re exclude the support-annotations dependency from here to avoid clashes
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
// exclude a couple of more modules here because of <http://stackoverflow.com/questions/29216327> and
// more specifically of <https://code.google.com/p/android-test-kit/issues/detail?id=139>
// otherwise you'll receive weird crashes on devices and dex exceptions on emulators
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.android.support', module: 'design'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
//excluded specific packages due to https://code.google.com/p/android/issues/detail?id=183454
androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
androidTestCompile('com.android.support.test.espresso:espresso-web:2.2.2') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
androidTestCompile('com.android.support.test:runner:0.5') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
androidTestCompile('com.android.support.test:rules:0.5') {
exclude group: 'com.android.support', module: 'support-annotations'
exclude module: 'support-annotations'
exclude module: 'recyclerview-v7'
exclude module: 'support-v4'
exclude module: 'support-v7'
}
Outre ces importations, il est nécessaire d’ajouter le testeur d’instrumentation Android à build.gradle android.defaultConfig:
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
Configuration de l'appareil
Pour un test non feuilleté, il est recommandé de définir les paramètres suivants sur vos appareils:
- Options pour les développeurs / Désactiver les animations - réduit la fragilité des tests
- Options pour les développeurs / Restez éveillé - si vous avez des appareils dédiés aux tests, cela est utile
- Options du développeur / Taille du tampon de l'enregistreur - définissez un nombre plus élevé si vous exécutez des suites de test très volumineuses sur votre téléphone
- Délai d'accessibilité / Touch & Hold - long pour éviter les problèmes de tapotement dans l'espresso
Assez une configuration du monde réel ha? Eh bien, maintenant, quand c'est hors de portée permet de jeter un oeil à la configuration d'un petit test
Écrire le test
Supposons que nous avons l'écran suivant: L'écran contient:
- champ de saisie de texte - R.id.textEntry
- bouton qui montre le snack avec le texte tapé quand cliqué - R.id.shownSnackbarBtn
- snackbar qui devrait contenir le texte saisi par l'utilisateur - android.support.design.R.id.snackbar_text
Maintenant, créons une classe qui testera notre flux:
/**
* Testing of the snackbar activity.
**/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class SnackbarActivityTest{
//espresso rule which tells which activity to start
@Rule
public final ActivityTestRule<SnackbarActivity> mActivityRule =
new ActivityTestRule<>(SnackbarActivity.class, true, false);
@Override
public void tearDown() throws Exception {
super.tearDown();
//just an example how tear down should cleanup after itself
mDatabase.clear();
mSharedPrefs.clear();
}
@Override
public void setUp() throws Exception {
super.setUp();
//setting up your application, for example if you need to have a user in shared
//preferences to stay logged in you can do that for all tests in your setup
User mUser = new User();
mUser.setToken("randomToken");
}
/**
*Test methods should always start with "testXYZ" and it is a good idea to
*name them after the intent what you want to test
**/
@Test
public void testSnackbarIsShown() {
//start our activity
mActivityRule.launchActivity(null);
//check is our text entry displayed and enter some text to it
String textToType="new snackbar text";
onView(withId(R.id.textEntry)).check(matches(isDisplayed()));
onView(withId(R.id.textEntry)).perform(typeText(textToType));
//click the button to show the snackbar
onView(withId(R.id.shownSnackbarBtn)).perform(click());
//assert that a view with snackbar_id with text which we typed and is displayed
onView(allOf(withId(android.support.design.R.id.snackbar_text),
withText(textToType))) .check(matches(isDisplayed()));
}
}
Comme vous l'avez remarqué, il y a 3 ou 4 choses que vous remarquerez souvent:
onView (withXYZ) <- viewMatchers avec eux, vous pouvez trouver des éléments à l'écran
effectuer (click ()) <- viewActions, vous pouvez exécuter des actions sur des éléments trouvés précédemment
check (correspond à (isDisplayed ())) <- viewAssertions, vérifications à effectuer sur les écrans précédemment trouvés
Tous ces éléments et bien d'autres peuvent être trouvés ici: https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/index.html
Thats it, maintenant vous pouvez exécuter le test soit avec un clic droit sur le nom de la classe / test et en sélectionnant Exécuter le test ou avec la commande:
./gradlew connectedFLAVORNAMEAndroidTest
La navigation
@Test
public void testUpNavigation() {
intending(hasComponent(ParentActivity.class.getName())).respondWith(new Instrumentation.ActivityResult(0, null));
onView(withContentDescription("Navigate up")).perform(click());
intended(hasComponent(ParentActivity.class.getName()));
}
Notez qu'il s'agit d'une solution de contournement et qu'elle entrera en conflit avec d'autres vues ayant la même description de contenu.
Effectuer une action sur une vue
Il est possible d'effectuer des ViewActions
sur une vue en utilisant la méthode perform.
La classe ViewActions
fournit des méthodes d'assistance pour les actions les plus courantes, telles que:
ViewActions.click()
ViewActions.typeText()
ViewActions.clearText()
Par exemple, pour cliquer sur la vue:
onView(...).perform(click());
onView(withId(R.id.button_simple)).perform(click());
Vous pouvez exécuter plusieurs actions avec un seul appel:
onView(...).perform(typeText("Hello"), click());
Si la vue avec laquelle vous travaillez est située dans un ScrollView
(vertical ou horizontal), considérez les actions précédentes qui nécessitent l'affichage de la vue (comme click()
et typeText()
) avec scrollTo()
. Cela garantit que la vue est affichée avant de passer à l'autre action:
onView(...).perform(scrollTo(), click());
Trouver une vue avec onView
Avec ViewMatchers
vous pouvez voir la vue dans la hiérarchie de la vue actuelle.
Pour rechercher une vue, utilisez la méthode onView()
avec un observateur de vue qui sélectionne la vue correcte. Les méthodes onView()
renvoient un objet de type ViewInteraction
.
Par exemple, trouver une vue par son R.id
est aussi simple que:
onView(withId(R.id.my_view))
Trouver une vue avec un texte:
onView(withText("Hello World"))
Espresso personnalisés
Espresso par défaut a beaucoup de correspondances qui vous aident à trouver des vues dont vous avez besoin pour faire des vérifications ou des interactions avec eux.
Les plus importants peuvent être trouvés dans la feuille de triche suivante:
https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/
Voici quelques exemples d’apparieurs:
- avecId (R.id.ID_of_object_you_are_looking_for);
- withText ("Du texte que vous attendez d'un objet");
- isDisplayed () <- check est la vue visible
- doesNotExist () <- vérifie que la vue n'existe pas
Tous ces éléments sont très utiles pour un usage quotidien, mais si vous avez des vues plus complexes, les personnaliseurs peuvent rendre les tests plus lisibles et les réutiliser à différents endroits.
Il y a 2 types les plus communs d'allumeurs que vous pouvez étendre: TypeSafeMatcher BoundedMatcher
L'implémentation de TypeSafeMatcher nécessite que vous vérifiiez l'instance de la vue à laquelle vous vous engagez, si le type correct correspond à certaines de ses propriétés par rapport à une valeur que vous avez fournie à un analyseur.
Par exemple, le gestionnaire de type sécurisé qui valide une vue d'image peut être dessiné correctement:
public class DrawableMatcher extends TypeSafeMatcher<View> {
private @DrawableRes final int expectedId;
String resourceName;
public DrawableMatcher(@DrawableRes int expectedId) {
super(View.class);
this.expectedId = expectedId;
}
@Override
protected boolean matchesSafely(View target) {
//Type check we need to do in TypeSafeMatcher
if (!(target instanceof ImageView)) {
return false;
}
//We fetch the image view from the focused view
ImageView imageView = (ImageView) target;
if (expectedId < 0) {
return imageView.getDrawable() == null;
}
//We get the drawable from the resources that we are going to compare with image view source
Resources resources = target.getContext().getResources();
Drawable expectedDrawable = resources.getDrawable(expectedId);
resourceName = resources.getResourceEntryName(expectedId);
if (expectedDrawable == null) {
return false;
}
//comparing the bitmaps should give results of the matcher if they are equal
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Bitmap otherBitmap = ((BitmapDrawable) expectedDrawable).getBitmap();
return bitmap.sameAs(otherBitmap);
}
@Override
public void describeTo(Description description) {
description.appendText("with drawable from resource id: ");
description.appendValue(expectedId);
if (resourceName != null) {
description.appendText("[");
description.appendText(resourceName);
description.appendText("]");
}
}
}
L'utilisation du matcher pourrait être comme ceci:
public static Matcher<View> withDrawable(final int resourceId) {
return new DrawableMatcher(resourceId);
}
onView(withDrawable(R.drawable.someDrawable)).check(matches(isDisplayed()));
Les corrélateurs liés sont similaires mais vous n'avez pas à faire de vérification de type mais, puisque cela se fait automatiquement pour vous:
/**
* Matches a {@link TextInputFormView}'s input hint with the given resource ID
*
* @param stringId
* @return
*/
public static Matcher<View> withTextInputHint(@StringRes final int stringId) {
return new BoundedMatcher<View, TextInputFormView>(TextInputFormView.class) {
private String mResourceName = null;
@Override
public void describeTo(final Description description) {
//fill these out properly so your logging and error reporting is more clear
description.appendText("with TextInputFormView that has hint ");
description.appendValue(stringId);
if (null != mResourceName) {
description.appendText("[");
description.appendText(mResourceName);
description.appendText("]");
}
}
@Override
public boolean matchesSafely(final TextInputFormView view) {
if (null == mResourceName) {
try {
mResourceName = view.getResources().getResourceEntryName(stringId);
} catch (Resources.NotFoundException e) {
throw new IllegalStateException("could not find string with ID " + stringId, e);
}
}
return view.getResources().getString(stringId).equals(view.getHint());
}
};
}
Plus sur les matchers peuvent être lus sur:
https://developer.android.com/reference/android/support/test/espresso/matcher/ViewMatchers.html
Espresso globale
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
ViewMatchers - Une collection d'objets qui implémentent Matcher<? super View>
interface. Vous pouvez en transmettre une ou plusieurs à la méthode onView
pour localiser une vue dans la hiérarchie de vues actuelle.
ViewActions - Collection de ViewActions
pouvant être transmise à la méthode ViewInteraction.perform()
(par exemple, click()
).
ViewAssertions - Une collection de ViewAssertions
pouvant être passée à la méthode ViewInteraction.check()
. La plupart du temps, vous utiliserez l'assertion de correspondances, qui utilise un analyseur de vues pour déterminer l'état de la vue actuellement sélectionnée.
Feuillet d'espresso par google
Entrer du texte dans EditText
onView(withId(R.id.edt_name)).perform(typeText("XYZ"));
closeSoftKeyboard();
Effectuez Cliquez sur Afficher
onView(withId(R.id.btn_id)).perform(click());
La vue de vérification est affichée
onView(withId(R.id.edt_pan_number)).check(ViewAssertions.matches((isDisplayed())));
Grouper une collection de classes de test dans une suite de tests
Vous pouvez organiser l'exécution de vos tests unitaires instrumentés définissant une Suite .
/**
* Runs all unit tests.
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({MyTest1.class ,
MyTest2.class,
MyTest3.class})
public class AndroidTestSuite {}
Ensuite, dans AndroidStudio, vous pouvez exécuter avec gradle ou définir une nouvelle configuration comme:
Les suites de tests peuvent être imbriquées.