23/03/2024
Asigurarea calității și fiabilității aplicațiilor Android este un aspect crucial în procesul de dezvoltare. Într-un ecosistem dinamic și competitiv, o aplicație care funcționează impecabil și oferă o experiență de utilizare fluidă se distinge. Aici intervine testarea interfeței de utilizator (UI), o componentă indispensabilă a ciclului de dezvoltare software. Dintre multitudinea de instrumente disponibile, Espresso se impune ca un cadru de testare UI de top, dezvoltat de Google, care permite dezvoltatorilor să scrie teste UI automate într-un mod eficient și intuitiv.

Acest ghid detaliază aspectele tehnice, pașii de implementare, exemple de cod, cele mai bune practici și sfaturi pentru depanare, oferind o perspectivă cuprinzătoare asupra scrierii de teste UI eficiente folosind Espresso. Indiferent dacă sunteți un dezvoltator la început de drum sau unul experimentat, veți găsi informații valoroase pentru a îmbunătăți procesul de testare al aplicațiilor dumneavoastră.
Ce este Espresso și de ce este esențial pentru testarea UI?
Espresso este un framework de testare dedicat, conceput special pentru a simula interacțiunile utilizatorilor cu interfața grafică a unei aplicații Android. Spre deosebire de alte abordări, Espresso rulează testele direct pe un dispozitiv real sau pe un emulator, mimând comportamentul unui utilizator real. Această abordare bazată pe instrumentație asigură că testele sunt executate în condiții cât mai apropiate de cele reale, oferind rezultate precise și relevante.
Unul dintre principalele avantaje ale Espresso este API-ul său simplu și extensibil, care facilitează scrierea de teste concise și ușor de înțeles. Mai mult, Espresso se remarcă prin capacitatea sa de a sincroniza automat acțiunile de testare cu evenimentele UI ale aplicației. Această sincronizare automată elimină necesitatea de a adăuga "sleeps" sau așteptări explicite în codul de test, reducând "flakiness-ul" testelor și crescând fiabilitatea acestora. Informațiile detaliate despre erori, oferite de Espresso în caz de eșec, contribuie la o depanare rapidă și eficientă.
În multe cercuri, Espresso este considerat un înlocuitor complet pentru alte cadre de testare, cum ar fi Robotium, datorită eficienței și robusteții sale superioare în testarea UI Testing.
Concepte Fundamentale și Terminologie Espresso
Pentru a înțelege cum funcționează Espresso, este esențial să familiarizați cu următoarele concepte cheie:
- View: Orice componentă UI (de exemplu, un buton, un câmp de text, o imagine) cu care utilizatorul poate interacționa.
- ViewGroup: Un container care poate deține mai multe View-uri, organizându-le pe ecran (de exemplu, LinearLayout, RelativeLayout).
- Matcher: O clasă fundamentală în Espresso, utilizată pentru a defini cum să se "potrivească" sau să se identifice o vizualizare specifică sau un set de vizualizări în ierarhia UI. Exemple includ
withId(),withText(),isDisplayed(). - Action: O acțiune care este efectuată pe o vizualizare sau un set de vizualizări. Exemple includ
click(),typeText(),scrollTo(). - Assertion: O verificare a comportamentului unei vizualizări sau a unui set de vizualizări. Acestea confirmă că UI-ul se află într-o stare așteptată după o acțiune. Exemple includ
matches(isDisplayed()),matches(withText("Hello")).
Cum funcționează Espresso sub capotă? Espresso utilizează o abordare bazată pe Matchers pentru a interacționa cu componentele UI și a verifica comportamentul acestora. Acesta își injectează acțiunile de testare direct în coada de evenimente UI a aplicației, asigurându-se că interacțiunile sunt executate doar atunci când aplicația este într-o stare stabilă, fără operațiuni asincrone în desfășurare.
Configurarea Proiectului Android Studio pentru Espresso
Pentru a începe cu Espresso, trebuie să efectuați câțiva pași de configurare în proiectul dumneavoastră Android Studio:
1. Structura Directorului de Teste
Asigurați-vă că aveți directorul app/src/androidTest/java. Acesta este locația implicită pentru testele de instrumentație. Este o bună practică să replicați aceeași structură de pachete în directorul de teste ca și în codul produsului, oferind testelor acces la câmpurile package-private din codul aplicației.

2. Dependențe Gradle
Adăugați următoarele dependențe în fișierul build.gradle al modulului aplicației (de obicei app/build.gradle). Este crucial să specificați testInstrumentationRunner în blocul defaultConfig.
android { defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } } dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // Dacă doriți să testați RecyclerViews, adăugați: androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1' { exclude group: 'com.android.support', module: 'appcompat' exclude group: 'com.android.support', module: 'support-v4' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'recyclerview-v7' } // Dacă doriți să "stubb-ui" intențiile (ex: Camera, Galerie), adăugați: androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1' { exclude group: 'com.android.support', module: 'support-annotations' } } Notă: Versiunile pot varia. Asigurați-vă că utilizați cele mai recente versiuni compatibile.
3. Dezactivarea Animațiilor de Sistem
Deoarece Espresso este un framework de testare UI, animațiile de sistem pot introduce instabilitate (flakiness) în teste. Se recomandă insistent dezactivarea acestora pe dispozitivul sau emulatorul utilizat pentru testare. Puteți face acest lucru din Settings > Developer options, dezactivând următoarele 3 setări și apoi repornind dispozitivul/emulatorul:
- Window animation scale
- Transition animation scale
- Animator duration scale
Scrierea Primului Test Espresso
Să creăm un test simplu care introduce text într-un EditText și apoi verifică textul introdus. Acest exemplu se bazează pe un proiect standard nou, care are o singură MainActivity.
Presupunem că MainActivity conține un EditText cu ID-ul R.id.etInput și un TextView cu ID-ul R.id.tvResult.
package com.yourpackage.yourapp; import androidx.test.espresso.Espresso; import androidx.test.espresso.action.ViewActions; import androidx.test.espresso.assertion.ViewAssertions; import androidx.test.espresso.matcher.ViewMatchers; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class MainActivityEspressoTest { // Regula JUnit 4 preferată pentru specificarea activității de lansat înainte de fiecare test @Rule public ActivityScenarioRule<MainActivity> activityRule = new ActivityScenarioRule<>(MainActivity.class); @Test public void validateEditTextInputAndTextViewContent() { // Caută un EditText cu id = R.id.etInput // Introduce textul "Hello Espresso!" în EditText Espresso.onView(ViewMatchers.withId(R.id.etInput)) .perform(ViewActions.typeText("Hello Espresso!"), ViewActions.closeSoftKeyboard()); // Verifică dacă EditText-ul are textul "Hello Espresso!" Espresso.onView(ViewMatchers.withId(R.id.etInput)) .check(ViewAssertions.matches(ViewMatchers.withText("Hello Espresso!"))); // Presupunând că introducerea în etInput actualizează tvResult, verificăm tvResult Espresso.onView(ViewMatchers.withId(R.id.tvResult)) .check(ViewAssertions.matches(ViewMatchers.withText("Hello Espresso!"))); } @Test public void testButtonClickAndTextUpdate() { // Presupunând că aveți un buton cu id R.id.btnSubmit și un TextView R.id.tvMessage Espresso.onView(ViewMatchers.withId(R.id.btnSubmit)) .perform(ViewActions.click()); Espresso.onView(ViewMatchers.withId(R.id.tvMessage)) .check(ViewAssertions.matches(ViewMatchers.withText("Mesaj trimis!"))); } } Anatomia unui Test Espresso
Când scrieți teste Espresso, veți folosi o mulțime de importuri statice. Acest lucru face codul mai ușor de citit. Să analizăm părțile testului de mai sus:
Espresso: Acesta este punctul de intrare principal. Cel mai des veți folosiEspresso.onView(...)pentru a specifica vizualizarea cu care doriți să interacționați.ViewMatchers: Aceasta este modalitatea prin care găsim vizualizări.ViewMatchersconține o colecție de Matchers Hamcrest care vă permit să găsiți vizualizări specifice în ierarhia UI. În exemplul de mai sus, am folositwithId(R.id.etInput)pentru a specifica că căutăm unEditTextcu ID-ulR.id.etInput. Alți matcheri comuni includwithText(),withContentDescription(),isDisplayed().ViewActions: Acesta este modul în care interacționăm cu vizualizările. Am folosit metodatypeText(...)pentru a introduce text înEditTextșiclick()pentru a simula un clic.closeSoftKeyboard()este util pentru a închide tastatura virtuală care poate acoperi alte elemente UI.ViewAssertions: Acesta este pasul nostru de validare. Utilizăm Asertări pentru a valida proprietăți specifice ale vizualizărilor. De cele mai multe ori veți folosiViewAssertionscare sunt susținute deViewMatchers. În exemplul nostru,withText(...)returnează de fapt unViewMatcherpe care l-am convertit într-unViewAssertionfolosind metodamatches(...).
Modelul standard pentru un test Espresso este: găsește o vizualizare (ViewMatchers), efectuează o Acțiune pe acea vizualizare (ViewActions) și apoi validează proprietățile vizualizării (ViewAssertions).
Scenarii de Testare Avansate cu Espresso
Interacțiunea cu un ListView
ListView este un AdapterView. AdapterViews prezintă o provocare la testarea UI, deoarece nu încarcă toate elementele la început (doar pe măsură ce utilizatorul derulează prin ele). Prin urmare, AdapterViews nu funcționează bine cu onView(...), deoarece vizualizarea particulară ar putea să nu facă parte încă din ierarhia vizualizărilor.
Din fericire, Espresso oferă punctul de intrare onData(...), care se asigură că elementul AdapterView este încărcat înainte de a efectua orice operațiune asupra sa.
import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; // ... în clasa de test ... @Test public void clickOnItemWithTextEqualToTwo() { // Găsește poziția adaptorului de apăsat pe baza potrivirii textului "doi" cu textul elementului adaptorului onData(allOf(is(instanceOf(String.class)), is("doi"))) .inAdapterView(withId(R.id.lvItems)) // Specificați ID-ul explicit al ListView-ului .perform(click()); // Acțiune standard ViewAction // Puteți adăuga o asertare aici, de exemplu, verificând un TextView care se actualizează // Espresso.onView(withId(R.id.selectedItemText)).check(matches(withText("doi"))); } @Test public void clickOnItemAtSpecificPosition() { // Specificați direct poziția în adaptor de apăsat onData(anything()) // Nu este nevoie să specificați un matcher de date dacă folosim poziția .inAdapterView(withId(R.id.lvItems)) // Specificați ID-ul explicit al ListView-ului .atPosition(1) // Specificați explicit elementul adaptorului de utilizat (poziția 1 = al doilea element) .perform(click()); // Acțiune standard ViewAction } Interacțiunea cu un RecyclerView
Din păcate, RecyclerView nu este un AdapterView, așa că nu putem folosi onData(...) pentru un RecyclerView. Cu toate acestea, Espresso suportă RecyclerView prin pachetul androidx.test.espresso.contrib (pe care l-am adăugat deja în Gradle).

import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition; import static androidx.test.espresso.matcher.ViewMatchers.withId; // ... în clasa de test ... @Test public void clickOnRecyclerViewItem() { // Apasă pe elementul RecyclerView de la poziția 2 onView(withId(R.id.rvItems)) .perform(actionOnItemAtPosition(2, click())); } @Test public void scrollToItemInRecyclerView() { // Derulează la un element specificat de o potrivire de text (dacă RecyclerView-ul conține TextView-uri) onView(withId(R.id.rvItems)) .perform(RecyclerViewActions.scrollTo(ViewMatchers.hasDescendant(ViewMatchers.withText("Element 50")))); } Simularea Interacțiunilor cu Alte Aplicații (Stubbing Camera)
O limitare a Espresso este că poate interacționa doar cu aplicația unică testată. Aceasta înseamnă că dacă aplicația dvs. lansează o altă aplicație (de exemplu, camera, galerie, contacte), codul dvs. de test Espresso nu va putea interacționa deloc cu cealaltă aplicație. Acest lucru devine problematic în scenarii în care o aplicație va lansa camera pentru a face o fotografie și apoi va returna imaginea rezultată aplicației originale.
Din fericire, Espresso oferă pachetul androidx.test.espresso.intent pentru a ne ajuta să "stubb-uim" (simulăm) intenția.
package com.yourpackage.yourapp; import android.app.Activity; import android.app.Instrumentation; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.provider.MediaStore; import androidx.test.espresso.intent.rule.IntentsTestRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.intent.Intents.intended; import static androidx.test.espresso.intent.Intents.intending; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage; import static androidx.test.espresso.matcher.ViewMatchers.withId; @RunWith(AndroidJUnit4.class) public class CameraActivityEspressoTest { // IntentsTestRule este o extensie a ActivityTestRule. Setează Espresso-Intents // înainte ca fiecare test să fie executat pentru a permite stubbing-ul și validarea intențiilor. @Rule public IntentsTestRule<CameraActivity> intentsRule = new IntentsTestRule<>(CameraActivity.class); @Test public void validateCameraScenario() { // Creează o bitmapă pe care o putem folosi pentru imaginea noastră simulată de cameră Bitmap icon = BitmapFactory.decodeResource( InstrumentationRegistry.getInstrumentation().getTargetContext().getResources(), R.mipmap.ic_launcher); // Construiește un rezultat de returnat de la aplicația Camera Intent resultData = new Intent(); resultData.putExtra("data", icon); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); // Stub-uiește Camera. Când o intenție este trimisă către Camera, acest lucru spune Espresso să răspundă // cu ActivityResult-ul pe care tocmai l-am creat. intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)) .respondWith(result); // Acum că avem stub-ul în loc, facem clic pe butonul din aplicația noastră care lansează Camera onView(withId(R.id.btnTakePicture)).perform(click()); // Putem valida, de asemenea, că o intenție care se rezolvă la activitatea "camera" a fost trimisă de aplicația noastră intended(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)); // ... pași de testare și validare suplimentari ... // De exemplu, verificați dacă imaginea a fost afișată într-un ImageView // onView(withId(R.id.imageView)).check(matches(ViewMatchers.isDisplayed())); } } Bune Practici și Optimizare
Considerații de Performanță
- Evitați utilizarea Espresso în firele UI principale: Espresso este conceput pentru a interacționa cu UI-ul, dar executarea operațiunilor complexe de testare direct în firul UI poate duce la blocaje și probleme de performanță. Testele Espresso rulează pe un fir separat de cel al aplicației.
- Folosiți Espresso în fire non-UI (sau background): Operațiunile de fundal care nu interacționează direct cu UI-ul ar trebui să fie rulate în fire separate pentru a nu bloca firul UI și a nu interfera cu Espresso. Espresso gestionează automat sincronizarea cu firul UI, dar este important ca aplicația să nu efectueze operații lungi pe firul UI.
Considerații de Securitate
Espresso poate fi utilizat pentru a testa aspecte legate de securitate prin verificarea comportamentului aplicației în anumite scenarii. De exemplu:
- Testarea autentificării: Asigurați-vă că procesele de autentificare funcționează corect și că utilizatorii neautorizați nu pot accesa anumite funcționalități.
- Verificarea permisiunilor: Testați că aplicația solicită și gestionează corect permisiunile necesare, iar funcționalitățile sensibile sunt protejate.
Sfaturi pentru Organizarea Codului
- Utilizați un director separat pentru teste: Păstrați testele organizate într-un director dedicat (
app/src/androidTest/java) pentru a le separa de codul sursă al aplicației. - Structură de pachete consistentă: Mențineți aceeași structură de pachete pentru testele dvs. ca și pentru codul produsului. Acest lucru ajută la accesul la elementele package-private și la o mai bună lizibilitate.
- Clase de utilitate pentru teste: Creați clase de utilitate sau helper-uri pentru acțiuni sau asertări complexe care sunt reutilizate în mai multe teste.
Greșeli Comune de Evitat
- Utilizarea "sleeps" sau așteptări explicite: Espresso gestionează automat sincronizarea cu firul UI. Adăugarea de
Thread.sleep()este o anti-practică și face testele instabile. - Testarea prea multor lucruri într-un singur test: Un test ar trebui să se concentreze pe o singură funcționalitate sau un singur scenariu. Acest lucru face testele mai ușor de înțeles și de depanat.
- Lipsa dezactivării animațiilor: Așa cum am menționat, animațiile pot cauza erori intermitente în teste.
- Ignorarea erorilor de sincronizare: Dacă Espresso raportează că nu poate găsi o vizualizare sau nu poate efectua o acțiune, verificați dacă există operațiuni asincrone (rețele, baze de date) care nu sunt gestionate corect de Espresso.
Testare și Depanare
Cum să Rulați Implementarea Testelor
Există două modalități principale de a rula testele Espresso:
- Prin Android Studio:
- Faceți clic dreapta pe clasa de testare sau pe o metodă de testare individuală în editor și selectați Run 'YourTestClass'.
- Asigurați-vă că selectați opțiunea de a rula testele folosind Gradle Test Runner dacă vi se prezintă mai multe opțiuni.
- Rezultatele pot fi vizualizate în fereastra Run (sau Console). Asigurați-vă că opțiunea Show Passed este activată pentru a vedea toate rezultatele.
- Prin Gradle:
- Deschideți fereastra Gradle (de obicei în partea dreaptă a Android Studio).
- Navigați la Tasks > verification și găsiți
connectedDebugAndroidTest. - Faceți clic dreapta și selectați Run.
- Acest lucru va genera un raport HTML detaliat al rezultatelor testelor la
app/build/reports/androidTests/connected/index.html. - Alternativ, puteți rula testele din linia de comandă folosind:
./gradlew connectedDebugAndroidTest.
Depanarea Testelor Espresso
Când un test eșuează, Espresso oferă informații detaliate. Iată câteva sfaturi pentru depanare:
- Citirea mesajelor de eroare: Mesajele de eroare Espresso sunt foarte descriptive. Ele vă vor spune ce vizualizare nu a putut fi găsită sau ce acțiune nu a putut fi efectuată și de ce.
- Utilizați Logcat: Monitorizați Logcat-ul în timpul executării testelor pentru a vedea erori, avertismente sau mesaje de depanare din aplicația dvs. sau din framework-ul Espresso.
- Capturi de ecran la eșec: Integrați o funcționalitate de captură de ecran la eșecul testelor. Acest lucru vă permite să vedeți starea UI exact în momentul în care testul a eșuat.
- Puncte de întrerupere (Breakpoints): Puteți seta puncte de întrerupere în codul de test și în codul aplicației și rula testele în modul de depanare pentru a inspecta starea variabilelor.
Probleme Comune și Soluții
| Problemă Comună | Soluție |
|---|---|
| Espresso nu găsește vizualizarea (View) |
|
| Espresso nu efectuează acțiunea |
|
| Teste instabile (flaky tests) |
|
Întrebări Frecvente (FAQ)
Este Espresso un înlocuitor pentru Robotium?
Da, în general, Espresso este considerat un înlocuitor superior pentru Robotium pentru testarea UI în Android. Espresso oferă o sincronizare automată mai bună cu firul UI al aplicației, ceea ce duce la teste mai fiabile și mai puțin "flaky". API-ul său este, de asemenea, mai simplu și mai intuitiv pentru majoritatea scenariilor de testare UI.
De ce trebuie să dezactivez animațiile pe dispozitiv/emulator?
Animațiile de sistem pot introduce întârzieri și incertitudini în executarea testelor UI. De exemplu, un element UI poate fi vizibil, dar nu încă "interactibil" din cauza unei animații în desfășurare. Dezactivarea acestora asigură că testele rulează mai rapid și sunt mai consistente, reducând riscul de eșecuri intermitente.
Pot testa interacțiuni cu alte aplicații (ex. lansarea camerei)?
Direct, Espresso interacționează doar cu aplicația dvs. sub test. Cu toate acestea, folosind modulul espresso-intents, puteți "stubb-ui" (simula) răspunsurile la intențiile lansate către alte aplicații. Acest lucru vă permite să testați fluxul aplicației dvs. fără a depinde de comportamentul real al aplicațiilor externe.
Cum gestionez liste mari de elemente (ListView/RecyclerView) în teste?
Pentru ListView (care este un AdapterView), ar trebui să utilizați Espresso.onData(), care este proiectat să interacționeze cu elemente care nu sunt neapărat vizibile pe ecran. Pentru RecyclerView, care nu este un AdapterView, trebuie să utilizați pachetul androidx.test.espresso.contrib și RecyclerViewActions, care oferă metode pentru a derula și a interacționa cu elementele din RecyclerView.
Concluzie
Espresso este un instrument puternic și indispensabil în arsenalul oricărui dezvoltator Android care își propune să creeze aplicații de înaltă calitate. Prin înțelegerea conceptelor sale fundamentale, aplicarea bunelor practici și utilizarea capabilităților sale avansate, puteți scrie teste UI robuste și fiabile care vor detecta erorile din timp și vor asigura o experiență de utilizator superioară. Investiția în testarea automată cu Espresso nu doar că economisește timp și resurse pe termen lung, dar contribuie și la construirea unei baze de cod stabile și ușor de întreținut. Începeți să integrați Espresso în fluxul dvs. de lucru astăzi și vedeți cum se transformă procesul de dezvoltare al aplicațiilor dvs. Android!
Dacă vrei să descoperi și alte articole similare cu Testarea UI Android cu Espresso: Ghid Complet, poți vizita categoria Fitness.
