Espresso Testing Framework - Kurzanleitung

Im Allgemeinen ist das Testen der mobilen Automatisierung eine schwierige und herausfordernde Aufgabe. Die Verfügbarkeit von Android für verschiedene Geräte und Plattformen macht das Testen der mobilen Automatisierung mühsam. Um dies zu vereinfachen, nahm Google die Herausforderung an und entwickelte ein Espresso-Framework. Es bietet eine sehr einfache, konsistente und flexible API zum Automatisieren und Testen der Benutzeroberflächen in einer Android-Anwendung. Espresso-Tests können sowohl in Java als auch in Kotlin geschrieben werden, einer modernen Programmiersprache zur Entwicklung von Android-Anwendungen.

Die Espresso-API ist einfach und leicht zu erlernen. Sie können problemlos Android-UI-Tests ohne die Komplexität von Multithread-Tests durchführen. Google Drive, Maps und einige andere Anwendungen verwenden derzeit Espresso.

Eigenschaften von Espresso

Einige der wichtigsten Funktionen, die von Espresso unterstützt werden, sind:

  • Sehr einfache API und daher leicht zu erlernen.

  • Hoch skalierbar und flexibel.

  • Bietet ein separates Modul zum Testen der Android WebView-Komponente.

  • Bietet ein separates Modul zum Überprüfen und Verspotten von Android-Absichten.

  • Bietet automatische Synchronisierung zwischen Ihrer Anwendung und Tests.

Vorteile von Espresso

Lassen Sie uns nun die Vorteile von Espresso erläutern.

  • Rückwärtskompatibilität

  • Einfach einzurichten.

  • Sehr stabiler Testzyklus.

  • Unterstützt Testaktivitäten auch außerhalb der Anwendung.

  • Unterstützt JUnit4

  • UI-Automatisierung zum Schreiben von Black-Box-Tests.

In diesem Kapitel erfahren Sie, wie Sie das Espresso-Framework installieren, es so konfigurieren, dass Espresso-Tests geschrieben und in unserer Android-Anwendung ausgeführt werden.

Voraussetzungen

Espresso ist ein Framework zum Testen von Benutzeroberflächen zum Testen von Android-Anwendungen, die in Java / Kotlin-Sprache mit Android SDK entwickelt wurden. Daher besteht die einzige Anforderung von Espresso darin, die Anwendung mit dem Android SDK in Java oder Kotlin zu entwickeln. Es wird empfohlen, über das neueste Android Studio zu verfügen.

Die Liste der Elemente, die ordnungsgemäß konfiguriert werden müssen, bevor wir mit der Arbeit im Espresso-Framework beginnen, lautet wie folgt:

  • Installieren Sie das neueste Java JDK und konfigurieren Sie die Umgebungsvariable JAVA_HOME.

  • Installieren Sie das neueste Android Studio (Version 3.2 oder höher).

  • Installieren Sie das neueste Android SDK mit dem SDK Manager und konfigurieren Sie die Umgebungsvariable ANDROID_HOME.

  • Installieren Sie das neueste Gradle Build Tool und konfigurieren Sie die Umgebungsvariable GRADLE_HOME.

Konfigurieren Sie das EspressoTesting Framework

Zunächst wird das Espresso-Test-Framework als Teil der Android-Support-Bibliothek bereitgestellt. Später stellt das Android-Team eine neue Android-Bibliothek, AndroidX, zur Verfügung und verschiebt die neueste Entwicklung des Espresso-Test-Frameworks in die Bibliothek. Die neueste Entwicklung (Android 9.0, API-Level 28 oder höher) des Espresso-Test-Frameworks wird in der AndroidX-Bibliothek durchgeführt.

Das Einfügen eines Espresso-Testframeworks in ein Projekt ist so einfach wie das Festlegen des Espresso-Testframeworks als Abhängigkeit in der Anwendungsgradle-Datei app / build.gradle. Die vollständige Konfiguration ist wie folgt:

Verwenden der Android-Unterstützungsbibliothek,

android {
   defaultConfig {
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
}
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}

Verwenden der AndroidX-Bibliothek,

android {
   defaultConfig {
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
}
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.androidx.test:runner:1.0.2'
   androidTestImplementation 'com.androidx.espresso:espresso-core:3.0.2'
}

testInstrumentationRunner in der android / defaultConfig legt die AndroidJUnitRunner- Klasse fest, um die Instrumentierungstests auszuführen. Die erste Zeile in den Abhängigkeiten enthält das JUnit- Testframework, die zweite Zeile in den Abhängigkeiten enthält die Testrunner-Bibliothek zum Ausführen der Testfälle und schließlich enthält die dritte Zeile in den Abhängigkeiten das Espresso-Testframework.

Standardmäßig legt Android Studio das Espresso-Test-Framework (Android-Unterstützungsbibliothek) als Abhängigkeit fest, während das Android-Projekt erstellt wird, und gradle lädt die erforderliche Bibliothek aus dem Maven-Repository herunter. Lassen Sie uns eine einfache Android-Anwendung von Hello World erstellen und prüfen, ob das Espresso-Testframework ordnungsgemäß konfiguriert ist.

Die Schritte zum Erstellen einer neuen Android-Anwendung werden nachfolgend beschrieben:

  • Starten Sie Android Studio.

  • Wählen Sie Datei → Neu → Neues Projekt.

  • Geben Sie den Anwendungsnamen (HelloWorldApp) und die Unternehmensdomäne (espressosamples.tutorialspoint.com) ein und klicken Sie dann auf Weiter .

So erstellen Sie ein Android-Projekt:

  • Wählen Sie die Mindest-API als API 15: Android 4.0.3 (IceCreamSandwich) aus und klicken Sie dann auf Weiter.

Um auf Android-Geräte abzuzielen,

  • Wählen Sie Leere Aktivität und klicken Sie dann auf Weiter .

Um Mobile eine Aktivität hinzuzufügen,

  • Geben Sie den Namen für die Hauptaktivität ein und klicken Sie dann auf Fertig stellen .

So konfigurieren Sie die Aktivität:

  • Sobald ein neues Projekt erstellt wurde, öffnen Sie die Datei app / build.gradle und überprüfen Sie deren Inhalt. Der Inhalt der Datei ist unten angegeben,

apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.tutorialspoint.espressosamples.helloworldapp"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
      release {
         minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'),    'proguard-rules.pro'
      }
   }
}
dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:28.0.0'
   implementation 'com.android.support.constraint:constraint-layout:1.1.3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}

Die letzte Zeile gibt die Abhängigkeit des Espresso-Test-Frameworks an. Standardmäßig ist die Android-Unterstützungsbibliothek konfiguriert. Sie können die Anwendung für die Verwendung der AndroidX- Bibliothek neu konfigurieren , indem Sie im Menü auf RefactorZu AndroidX migrieren klicken .

Um auf Androidx zu migrieren,

  • Jetzt ändert sich die app / build.gradle wie unten angegeben.

apply plugin: 'com.android.application'
android {
   compileSdkVersion 28
   defaultConfig {
      applicationId "com.tutorialspoint.espressosamples.helloworldapp"
      minSdkVersion 15
      targetSdkVersion 28
      versionCode 1
      versionName "1.0"
      testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }
   buildTypes {
      release {
         minifyEnabled false
         proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      }
   }
}
dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
   implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

Jetzt enthält die letzte Zeile das Espresso-Test-Framework aus der AndroidX-Bibliothek.

Geräteeinstellungen

Während des Tests wird empfohlen, die Animation auf dem Android-Gerät zu deaktivieren, das zum Testen verwendet wird. Dies verringert die Verwirrungen beim Überprüfen der Leerlaufressourcen.

Lassen Sie uns sehen, wie Sie die Animation auf Android-Geräten deaktivieren - (Einstellungen → Entwickleroptionen),

  • Fensteranimationsskala

  • Übergangsanimationsskala

  • Animator-Dauer-Skala

Wenn das Menü " Entwickleroptionen" im Bildschirm " Einstellungen" nicht verfügbar ist , klicken Sie mehrmals in der Option " Über Telefon" auf " Build-Nummer verfügbar" . Dies aktiviert das Developer Option- Menü.

In diesem Kapitel erfahren Sie, wie Sie Tests mit Android Studio ausführen.

Jede Android-Anwendung hat zwei Arten von Tests -

  • Funktions- / Unit-Tests

  • Instrumentierungstests

Für den Funktionstest muss die eigentliche Android-Anwendung nicht auf dem Gerät oder Emulator installiert und gestartet und die Funktionalität getestet werden. Es kann in der Konsole selbst gestartet werden, ohne die eigentliche Anwendung aufzurufen. Bei Instrumentierungstests muss jedoch die eigentliche Anwendung gestartet werden, um die Funktionen wie Benutzeroberfläche und Benutzerinteraktion zu testen. Standardmäßig werden Unit-Tests geschriebensrc/test/java/ Ordner- und Instrumentierungstests sind in geschrieben src/androidTest/java/Mappe. Android Studio bietet das Kontextmenü Ausführen für die Testklassen, um den in den ausgewählten Testklassen geschriebenen Test auszuführen. Standardmäßig verfügt eine Android-Anwendung über zwei Klassen: ExampleUnitTest im Ordner src / test und ExampleInstrumentedTest im Ordner src / androidTest .

Um den Standard-Unit-Test auszuführen , wählen Sie im Android Studio ExampleUnitTest aus, klicken Sie mit der rechten Maustaste darauf und klicken Sie dann auf Run 'ExampleUnitTest' (siehe unten).

Unit Test ausführen

Dadurch wird der Komponententest ausgeführt und das Ergebnis in der Konsole wie im folgenden Screenshot angezeigt -

Unit Test Erfolg

Um den Standard-Instrumentationstest auszuführen, wählen Sie im Android Studio ExampleInstrumentationTest aus, klicken Sie mit der rechten Maustaste darauf und klicken Sie dann auf Run 'ExampleInstrumentationTest' (siehe unten).

Führen Sie den Instrumentationstest durch

Dadurch wird der Komponententest ausgeführt, indem die Anwendung entweder auf dem Gerät oder im Emulator gestartet wird und das Ergebnis in der Konsole wie im folgenden Screenshot angezeigt wird:

Der Instrumentierungstest lief erfolgreich.

Lassen Sie uns in diesem Kapitel die Grundlagen von JUnit verstehen , dem beliebten Unit-Testing-Framework, das von der Java-Community entwickelt wurde und auf dem das Espresso-Test-Framework basiert.

JUnit ist der De-facto-Standard für Unit-Tests einer Java-Anwendung. Obwohl es für Unit-Tests beliebt ist, bietet es vollständige Unterstützung und Bereitstellung für Instrumententests. Die Espresso-Testbibliothek erweitert die erforderlichen JUnit-Klassen, um die Android-basierten Instrumententests zu unterstützen.

Schreiben Sie einen einfachen Komponententest

Erstellen wir eine Java-Klasse, Computation (Computation.java), und schreiben Sie eine einfache mathematische Operation, Summation und Multiplikation . Anschließend schreiben wir Testfälle mit JUnit und überprüfen sie, indem wir die Testfälle ausführen.

  • Starten Sie Android Studio.

  • Öffnen Sie die im vorherigen Kapitel erstellte HelloWorldApp .

  • Erstellen Sie eine Datei, Computation.java in app / src / main / java / com / tutorialspoint / espressosamples / HelloWorldApp / und Schreib zwei Funktionen - Summe und Multiplizieren , wie unten angegeben,

package com.tutorialspoint.espressosamples.helloworldapp;
public class Computation {
   public Computation() {}
   public int Sum(int a, int b) {
      return a + b;
   }
   public int Multiply(int a, int b) {
      return a * b;
   }
}
  • Erstellen Sie eine Datei, ComputationUnitTest.java, in app / src / test / java / com / tutorialspoint / espressosamples / helloworldapp und schreiben Sie Unit-Testfälle, um die Summen- und Multiplikationsfunktionalität wie unten angegeben zu testen

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
   @Test
   public void sum_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Multiply(2,2));
   }
}

Hier haben wir zwei neue Begriffe verwendet - @Test und assertEquals . Im Allgemeinen verwendet JUnit Java-Annotationen, um die Testfälle in einer Klasse zu identifizieren und Informationen zum Ausführen der Testfälle zu erhalten. @Test ist eine solche Java-Annotation, die angibt, dass die bestimmte Funktion ein Junit-Testfall ist. assertEquals ist eine Funktion, mit der bestätigt wird, dass das erste Argument (erwarteter Wert) und das zweite Argument (berechneter Wert) gleich und gleich sind. JUnit bietet eine Reihe von Assertionsmethoden für verschiedene Testszenarien.

  • Führen Sie nun den ComputationUnitTest im Android Studio aus, indem Sie mit der rechten Maustaste auf die Klasse klicken und die Option 'ComputationUnitTest' ausführen aufrufen , wie im vorherigen Kapitel erläutert. Dadurch werden die Unit-Testfälle ausgeführt und der Erfolg gemeldet.

Das Ergebnis des Berechnungseinheitentests ist wie folgt:

Anmerkungen

Das JUnit-Framework verwendet Annotationen in großem Umfang . Einige der wichtigen Anmerkungen sind wie folgt:

  • @Test

  • @Before

  • @After

  • @BeforeClass

  • @AfterClass

  • @Rule

@ Test Annotation

@Test ist die sehr wichtige Anmerkung im JUnit- Framework. @Test wird verwendet, um eine normale Methode von der Testfallmethode zu unterscheiden. Sobald eine Methode mit der Annotation @Test versehen ist , wird diese bestimmte Methode als Testfall betrachtet und von JUnit Runner ausgeführt . JUnit Runner ist eine spezielle Klasse, mit der die in den Java-Klassen verfügbaren JUnit-Testfälle gefunden und ausgeführt werden. Derzeit verwenden wir die integrierte Option von Android Studio, um die Komponententests auszuführen (auf denen wiederum der JUnit Runner ausgeführt wird ). Ein Beispielcode lautet wie folgt:

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   @Test
   public void multiply_isCorrect() {
      Computation computation = new Computation();
      assertEquals(4, computation.Multiply(2,2));
   }
}

@Vor

@Before Annotation wird verwendet, um auf eine Methode zu verweisen, die aufgerufen werden muss, bevor eine in einer bestimmten Testklasse verfügbare Testmethode ausgeführt wird. In unserem Beispiel kann das Berechnungsobjekt beispielsweise in einer separaten Methode erstellt und mit @Before versehen werden, sodass es vor dem Testfall sum_isCorrect und multiply_isCorrect ausgeführt wird. Der vollständige Code lautet wie folgt:

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   Computation computation = null;
   @Before
   public void CreateComputationObject() {
      this.computation = new Computation();
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, this.computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, this.computation.Multiply(2,2));
   }
}

@Nach

@After ähnelt @Before , aber die mit @After kommentierte Methode wird aufgerufen oder ausgeführt, nachdem jeder Testfall ausgeführt wurde. Der Beispielcode lautet wie folgt:

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   Computation computation = null;
   @Before
   public void CreateComputationObject() {
      this.computation = new Computation();
   }
   @After
   public void DestroyComputationObject() {
      this.computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, this.computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, this.computation.Multiply(2,2));
   }
}

@Vor dem Unterricht

@BeforeClass ähnelt @Before , aber die mit @BeforeClass annotierte Methode wird nur einmal aufgerufen oder ausgeführt, bevor alle Testfälle in einer bestimmten Klasse ausgeführt werden. Es ist nützlich, ein ressourcenintensives Objekt wie ein Datenbankverbindungsobjekt zu erstellen. Dies verkürzt die Zeit zum Ausführen einer Sammlung von Testfällen. Diese Methode muss statisch sein, damit sie ordnungsgemäß funktioniert. In unserem Beispiel können wir das Berechnungsobjekt einmal erstellen, bevor wir alle unten angegebenen Testfälle ausführen.

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

@Nach dem Unterricht

@AfterClass ähnelt @BeforeClass , aber die mit @AfterClass kommentierte Methode wird nur einmal aufgerufen oder ausgeführt, nachdem alle Testfälle in einer bestimmten Klasse ausgeführt wurden. Diese Methode muss auch statisch sein, um ordnungsgemäß zu funktionieren. Der Beispielcode lautet wie folgt:

package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @AfterClass
   public static void DestroyComputationObject() {
      computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

@Regel

Die @ Regel- Annotation ist eines der Highlights von JUnit . Es wird verwendet, um den Testfällen Verhalten hinzuzufügen. Wir können nur die Felder vom Typ TestRule mit Anmerkungen versehen . Es bietet tatsächlich Funktionen, die von @Before und @After Annotation bereitgestellt werden, jedoch auf effiziente und wiederverwendbare Weise. Beispielsweise benötigen wir möglicherweise einen temporären Ordner, um einige Daten während eines Testfalls zu speichern. Normalerweise müssen wir einen temporären Ordner erstellen, bevor wir den Testfall ausführen (entweder mit der Annotation @Before oder @BeforeClass) und ihn zerstören, nachdem der Testfall ausgeführt wurde (entweder mit der Annotation @After oder @AfterClass). Stattdessen können wir die vom JUnit- Framework bereitgestellte TemporaryFolder- Klasse (vom Typ TestRule ) verwenden , um einen temporären Ordner für alle unsere Testfälle zu erstellen. Der temporäre Ordner wird gelöscht, sobald der Testfall ausgeführt wird. Wir müssen eine neue Variable vom Typ TemporaryFolder erstellen und mit @Rule kommentieren, wie unten angegeben.

package com.tutorialspoint.espressosamples.helloworldapp;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

public class ComputationUnitTest {
   private static Computation computation = null;
   @Rule
   public TemporaryFolder folder = new TemporaryFolder();
   @Test
   public void file_isCreated() throws IOException {
      folder.newFolder("MyTestFolder");
      File testFile = folder.newFile("MyTestFile.txt");
      assertTrue(testFile.exists());
   }
   @BeforeClass
   public static void CreateComputationObject() {
      computation = new Computation();
   }
   @AfterClass
   public static void DestroyComputationObject() {
      computation = null;
   }
   @Test
   public void sum_isCorrect() {
      assertEquals(4, computation.Sum(2,2));
   }
   @Test
   public void multiply_isCorrect() {
      assertEquals(4, computation.Multiply(2,2));
   }
}

Ausführungsreihenfolge

In JUnit werden die mit unterschiedlichen Anmerkungen versehenen Methoden in einer bestimmten Reihenfolge ausgeführt, wie unten gezeigt.

  • @BeforeClass

  • @Rule

  • @Before

  • @Test

  • @After

  • @AfterClass

Behauptung

Durch die Bestätigung kann überprüft werden, ob der erwartete Wert des Testfalls mit dem tatsächlichen Wert des Testergebnises übereinstimmt. JUnit bietet Zusicherungen für verschiedene Szenarien. Einige wichtige Aussagen sind unten aufgeführt -

  • fail() - Um einen Testfall explizit zum Scheitern zu bringen.

  • assertTrue(boolean test_condition) - Überprüft, ob die test_condition wahr ist

  • assertFalse(boolean test_condition) - Überprüft, ob die test_condition falsch ist

  • assertEquals(expected, actual) - Überprüft, ob beide Werte gleich sind

  • assertNull(object) - Überprüft, ob das Objekt null ist

  • assertNotNull(object) - Überprüft, ob das Objekt nicht null ist

  • assertSame(expected, actual) - Überprüft, ob beide auf dasselbe Objekt verweisen.

  • assertNotSame(expected, actual) - Überprüft, ob beide unterschiedliche Objekte referenzieren.

In diesem Kapitel lernen wir die Bedingungen des Espresso-Test-Frameworks, das Schreiben eines einfachen Espresso-Testfalls und den vollständigen Workflow oder die Architektur des Espresso-Test-Frameworks kennen.

Überblick

Espresso bietet eine große Anzahl von Klassen zum Testen der Benutzeroberfläche und der Benutzerinteraktion einer Android-Anwendung. Sie können wie unten angegeben in fünf Kategorien eingeteilt werden:

JUnit Läufer

Das Android-Testframework bietet einen Runner, AndroidJUnitRunner, zum Ausführen der Espresso-Testfälle, die in Testfällen im JUnit3- und JUnit4-Stil geschrieben wurden. Es ist spezifisch für Android-Anwendungen und behandelt das Laden der Espresso-Testfälle und der zu testenden Anwendung sowohl auf dem tatsächlichen Gerät als auch im Emulator transparent, führt die Testfälle aus und meldet das Ergebnis der Testfälle. Um AndroidJUnitRunner im Testfall zu verwenden, müssen wir die Testklasse mit der Annotation @RunWith kommentieren und dann das unten angegebene Argument AndroidJUnitRunner übergeben -

@RunWith(AndroidJUnit4.class)
   public class ExampleInstrumentedTest {
}

JUnit-Regeln

Das Android-Testframework bietet die Regel ActivityTestRule, mit der eine Android-Aktivität gestartet werden kann, bevor die Testfälle ausgeführt werden. Es startet die Aktivität vor jeder mit @ Test` und @Before annotierten Methode. Die Aktivität wird nach der mit @After kommentierten Methode beendet. Ein Beispielcode lautet wie folgt:

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

Hier ist MainActivity die Aktivität, die vor dem Ausführen eines Testfalls gestartet und nach dem Ausführen des jeweiligen Testfalls zerstört werden soll.

ViewMatchers

Espresso bietet eine große Anzahl von View Matcher-Klassen (im androidx.test.espresso.matcher.ViewMatchers-Paket ), um UI-Elemente / Ansichten in der Ansichtshierarchie eines Android-Aktivitätsbildschirms abzugleichen und zu finden. Die Espresso-Methode onView verwendet ein einzelnes Argument vom Typ Matcher (View Matchers), findet die entsprechende UI-Ansicht und gibt das entsprechende ViewInteraction- Objekt zurück. Das von der onView- Methode zurückgegebene ViewInteraction- Objekt kann weiterhin zum Aufrufen von Aktionen wie Klicken auf die übereinstimmende Ansicht oder zum Aktivieren der übereinstimmenden Ansicht verwendet werden. Ein Beispielcode zum Auffinden der Ansicht mit dem Text "Hallo Welt!" ist wie folgt,

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));

Hier ist withText ein Matcher, mit dem die UI-Ansicht mit dem Text "Hello World!"

ViewActions

Espresso bietet eine große Anzahl von Ansichtsaktionsklassen (in androidx.test.espresso.action.ViewActions), um die verschiedenen Aktionen für die ausgewählte / übereinstimmende Ansicht aufzurufen. Sobald onView mit dem ViewInteraction- Objekt übereinstimmt und es zurückgibt , kann jede Aktion aufgerufen werden, indem die Methode "perform" des ViewInteraction- Objekts aufgerufen und mit den richtigen Ansichtsaktionen übergeben wird. Ein Beispielcode zum Klicken auf die übereinstimmende Ansicht lautet wie folgt:

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.perform(click());

Hier wird die Klickaktion der übereinstimmenden Ansicht aufgerufen.

ViewAssertions

Ähnlich wie beim Anzeigen von Matchern und Ansichtsaktionen bietet Espresso eine große Anzahl von Ansichtszusicherungen (im Paket androidx.test.espresso.assertion.ViewAssertions ), um sicherzustellen , dass die übereinstimmende Ansicht unseren Erwartungen entspricht. Sobald onView mit dem ViewInteraction- Objekt übereinstimmt und es zurückgibt , kann jede Zusicherung mithilfe der Prüfmethode von ViewInteraction überprüft werden, indem sie mit der richtigen Ansichtszusicherung übergeben wird. Ein Beispielcode, um zu bestätigen, dass die übereinstimmende Ansicht wie folgt lautet:

ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.check(matches(withId(R.id.text_view)));

Hier akzeptieren Übereinstimmungen den View Matcher und geben die View Assertion zurück, die mit der Check-Methode von ViewInteraction überprüft werden kann .

Workflow des Espresso Testing Framework

Lassen Sie uns verstehen, wie das Espresso-Test-Framework funktioniert und wie es Optionen bietet, um jede Art von Benutzerinteraktion auf einfache und flexible Weise durchzuführen. Der Arbeitsablauf eines Espresso-Testfalls ist wie folgt beschrieben:

  • Wie wir zuvor erfahren haben, wird Android JUnit Runner, AndroidJUnit4 die Android-Testfälle ausführen. Die Espresso-Testfälle müssen mit @RunWith (AndroidJUnut.class) gekennzeichnet sein . Zunächst bereitet AndroidJUnit4 die Umgebung für die Ausführung der Testfälle vor. Es startet entweder das angeschlossene Android-Gerät oder den Emulator, installiert die Anwendung und stellt sicher, dass die zu testende Anwendung bereit ist. Es werden die Testfälle ausgeführt und die Ergebnisse gemeldet.

  • Espresso benötigt mindestens eine einzelne JUnit- Regel vom Typ ActivityTestRule , um die Aktivität anzugeben. Android JUnit Runner startet die Aktivität, die mit ActivityTestRule gestartet werden soll .

  • Jeder Testfall benötigt mindestens einen einzelnen Aufruf der Methode onView oder onDate (zum Auffinden datenbasierter Ansichten wie AdapterView ), um die gewünschte Ansicht zu finden. onView oder onData geben das ViewInteraction- Objekt zurück.

  • Sobald das ViewInteraction- Objekt zurückgegeben wird, können wir entweder eine Aktion der ausgewählten Ansicht aufrufen oder die Ansicht mithilfe der Zusicherung auf unsere erwartete Ansicht überprüfen.

  • Die Aktion kann mithilfe der Methode perform des ViewInteraction- Objekts aufgerufen werden, indem eine der verfügbaren Ansichtsaktionen übergeben wird.

  • Die Zusicherung kann mithilfe der Prüfmethode des ViewInteraction- Objekts aufgerufen werden, indem eine der verfügbaren Ansichtszusicherungen übergeben wird.

Die Diagrammdarstellung des Workflows lautet wie folgt:

Beispiel - Ansicht Behauptung

Lassen Sie uns einen einfachen Testfall schreiben, um die Textansicht mit "Hallo Welt!" Text in unserer Anwendung „HelloWorldApp“ und bestätigen Sie ihn dann mithilfe der Ansichtszusicherung. Der vollständige Code lautet wie folgt:

package com.tutorialspoint.espressosamples.helloworldapp;

import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
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.matcher.ViewMatchers.withText;;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static org.junit.Assert.*;
/**
   * Instrumented test, which will execute on an Android device.
   *
   * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
   @Rule
   public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
   @Test
   public void view_isCorrect() {
      onView(withText("Hello World!")).check(matches(isDisplayed()));
   }
   @Test
   public void useAppContext() {
      // Context of the app under test.
      Context appContext = InstrumentationRegistry.getTargetContext();
      assertEquals("com.tutorialspoint.espressosamples.helloworldapp", appContext.getPackageName());
   }
}

Hier haben wir withText View Matcher verwendet, um die Textansicht mit "Hello World!" Zusicherung der Text- und Übereinstimmungsansicht, um sicherzustellen, dass die Textansicht ordnungsgemäß angezeigt wird. Sobald der Testfall in Android Studio aufgerufen wurde, wird der Testfall ausgeführt und die Erfolgsmeldung wie folgt gemeldet.

view_isKorrekter Testfall

Das Espresso-Framework bietet viele View Matcher. Der Zweck des Matchers besteht darin, eine Ansicht mit verschiedenen Attributen der Ansicht wie ID, Text und Verfügbarkeit der untergeordneten Ansicht abzugleichen. Jeder Matcher entspricht einem bestimmten Attribut der Ansicht und gilt für einen bestimmten Ansichtstyp. Beispielsweise stimmt der withId- Matcher mit der Id- Eigenschaft der Ansicht überein und gilt für alle Ansichten, während der withText-Matcher mit der Text- Eigenschaft der Ansicht übereinstimmt und nur für TextView gilt .

In diesem Kapitel lernen wir die verschiedenen Matcher kennen, die vom Espresso-Test-Framework bereitgestellt werden, sowie die Hamcrest- Bibliothek, auf der die Espresso-Matcher basieren.

Hamcrest Bibliothek

Die Hamcrest- Bibliothek ist eine wichtige Bibliothek im Rahmen des Espresso-Test-Frameworks. Hamcrest ist selbst ein Framework zum Schreiben von Matcher-Objekten. Das Espresso-Framework verwendet die Hamcrest- Bibliothek in großem Umfang und erweitert sie bei Bedarf, um einfache und erweiterbare Matcher bereitzustellen.

Hamcrest bietet eine einfache Funktion assertThat und eine Sammlung von Matchern, um Objekte zu bestätigen. assertThat hat drei Argumente und sie sind wie unten gezeigt -

  • String (Beschreibung des Tests, optional)

  • Objekt (aktuell)

  • Matcher (erwartet)

Schreiben wir ein einfaches Beispiel, um zu testen, ob ein Listenobjekt den erwarteten Wert hat.

import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
@Test
public void list_hasValue() {
   ArrayList<String> list = new ArrayList<String>();
   list.add("John");
   assertThat("Is list has John?", list, hasItem("John"));
}

Hier gibt hasItem einen Matcher zurück, der prüft, ob die tatsächliche Liste einen Wert als eines der Elemente angegeben hat.

Hamcrest verfügt über viele integrierte Matcher und Optionen zum Erstellen neuer Matcher. Einige der wichtigen integrierten Matcher, die im Espresso-Test-Framework nützlich sind, sind folgende:

alles - immer Matcher

Logisch basierte Matcher

  • allOf - Akzeptieren Sie eine beliebige Anzahl von Matchern und Matches nur, wenn alle Matcher erfolgreich sind.

  • anyOf - Akzeptieren Sie eine beliebige Anzahl von Matchern und Matches, wenn ein Matcher erfolgreich war.

  • not - Akzeptiere einen Matcher und stimme nur zu, wenn der Matcher fehlgeschlagen ist und umgekehrt.

Textbasierte Matcher

  • equalToIgnoringCase - Wird verwendet, um zu testen, ob die tatsächliche Eingabe dem erwarteten Fall entspricht, bei dem die Zeichenfolge ignoriert wird.

  • equalToIgnoringWhiteSpace - Wird verwendet, um zu testen, ob die tatsächliche Eingabe der angegebenen Zeichenfolge entspricht, wobei Groß- und Kleinschreibung und Leerzeichen ignoriert werden.

  • containsString - Wird verwendet, um zu testen, ob die tatsächliche Eingabe eine angegebene Zeichenfolge enthält.

  • endsWith - Wird verwendet, um zu testen, ob die tatsächliche Eingabe mit der angegebenen Zeichenfolge beginnt.

  • startsWith - Wird verwendet, um zu testen, ob die Eingabe tatsächlich mit der angegebenen Zeichenfolge endet.

Zahlenbasierte Matcher

  • closeTo - wird verwendet, um zu testen, ob die tatsächliche Eingabe nahe an der erwarteten Anzahl liegt.

  • greaterThan - wird verwendet, um zu testen, ob die tatsächliche Eingabe größer als die erwartete Anzahl ist.

  • greaterThanOrEqualTo - wird verwendet, um zu testen, ob die tatsächliche Eingabe größer oder gleich der erwarteten Anzahl ist.

  • lessThan - wird verwendet, um zu testen, ob die tatsächliche Eingabe kleiner als die erwartete Anzahl ist.

  • lessThanOrEqualTo - wird verwendet, um zu testen, ob die tatsächliche Eingabe kleiner oder gleich der erwarteten Anzahl ist.

Objektbasierte Matcher

  • equalTo - wird verwendet, um zu testen, ob die tatsächliche Eingabe dem erwarteten Objekt entspricht

  • hasToString - Wird verwendet, um zu testen, ob die tatsächliche Eingabe die toString-Methode hat.

  • instanceOf - wird verwendet, um zu testen, ob die tatsächliche Eingabe die Instanz der erwarteten Klasse ist.

  • isCompatibleType - Wird verwendet, um zu testen, ob die tatsächliche Eingabe mit dem erwarteten Typ kompatibel ist.

  • notNullValue - wird verwendet, um zu testen, ob die tatsächliche Eingabe nicht null ist.

  • sameInstance - Wird verwendet, um zu testen, ob die tatsächliche Eingabe und die erwartete Eingabe von derselben Instanz sind.

  • hasProperty - wird verwendet, um zu testen, ob die tatsächliche Eingabe die erwartete Eigenschaft hat

ist - Zucker oder Abkürzung für gleich

Matcher

Espresso bietet die onView () -Methode zum Abgleichen und Finden der Ansichten. Es akzeptiert Ansichtsabgleiche und gibt das ViewInteraction-Objekt zurück, um mit der übereinstimmenden Ansicht zu interagieren. Die häufig verwendete Liste der Ansichts-Matcher wird unten beschrieben -

withId ()

withId () akzeptiert ein Argument vom Typ int und das Argument verweist auf die ID der Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht anhand der ID der Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(withId(R.id.testView))

withText ()

withText () akzeptiert ein Argument vom Typ string und das Argument verweist auf den Wert der Texteigenschaft der Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht anhand des Textwerts der Ansicht entspricht. Dies gilt nur für TextView . Der Beispielcode lautet wie folgt:

onView(withText("Hello World!"))

withContentDescription ()

withContentDescription () akzeptiert ein Argument vom Typ string und das Argument verweist auf den Wert der Inhaltsbeschreibungseigenschaft der Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht anhand der Beschreibung der Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(withContentDescription("blah"))

Wir können auch die Ressourcen-ID des Textwerts anstelle des Textes selbst übergeben.

onView(withContentDescription(R.id.res_id_blah))

hasContentDescription ()

hasContentDescription () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Ansicht mit einer beliebigen Inhaltsbeschreibung entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), hasContentDescription()))

withTagKey ()

withTagKey () akzeptiert ein Argument vom Typ string und das Argument verweist auf den Tag-Schlüssel der Ansicht. Es wird ein Matcher zurückgegeben, der mit seinem Tag-Schlüssel mit der Ansicht übereinstimmt. Der Beispielcode lautet wie folgt:

onView(withTagKey("blah"))

Wir können auch die Ressourcen-ID des Tag-Namens anstelle des Tag-Namens selbst übergeben.

onView(withTagKey(R.id.res_id_blah))

withTagValue ()

withTagValue () akzeptiert ein Argument vom Typ Matcher <Objekt> und das Argument verweist auf den Tag-Wert der Ansicht. Es wird ein Matcher zurückgegeben, der anhand seines Tag-Werts mit der Ansicht übereinstimmt. Der Beispielcode lautet wie folgt:

onView(withTagValue(is((Object) "blah")))

Hier ist ist hamcrest Matcher.

withClassName ()

withClassName () akzeptiert ein Argument vom Typ Matcher <String> und das Argument verweist auf den Klassennamenwert der Ansicht. Es wird ein Matcher zurückgegeben, der mit seinem Klassennamen mit der Ansicht übereinstimmt. Der Beispielcode lautet wie folgt:

onView(withClassName(endsWith("EditText")))

Hier endet, ist Hamcrest Matcher und gibt Matcher <String> zurück

withHint ()

withHint () akzeptiert ein Argument vom Typ Matcher <String> und das Argument verweist auf den Hinweiswert der Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht anhand des Hinweises der Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(withClassName(endsWith("Enter name")))

withInputType ()

withInputType () akzeptiert ein Argument vom Typ int und das Argument verweist auf den Eingabetyp der Ansicht. Es wird ein Matcher zurückgegeben, der anhand seines Eingabetyps mit der Ansicht übereinstimmt. Der Beispielcode lautet wie folgt:

onView(withInputType(TYPE_CLASS_DATETIME))

Hier bezieht sich TYPE_CLASS_DATETIME auf die Bearbeitungsansicht, die Datum und Uhrzeit unterstützt.

withResourceName ()

withResourceName () akzeptiert ein Argument vom Typ Matcher <String> und das Argument verweist auf den Klassennamenwert der Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht unter Verwendung des Ressourcennamens der Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(withResourceName(endsWith("res_name")))

Es akzeptiert auch String-Argumente. Der Beispielcode lautet wie folgt:

onView(withResourceName("my_res_name"))

withAlpha ()

withAlpha () akzeptiert ein Argument vom Typ float und das Argument verweist auf den Alpha-Wert der Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht anhand des Alpha-Werts der Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(withAlpha(0.8))

withEffectiveVisibility ()

withEffectiveVisibility () akzeptiert ein Argument vom Typ ViewMatchers.Visibility und das Argument verweist auf die effektive Sichtbarkeit der Ansicht. Es wird ein Matcher zurückgegeben, der anhand der Sichtbarkeit der Ansicht mit der Ansicht übereinstimmt. Der Beispielcode lautet wie folgt:

onView(withEffectiveVisibility(withEffectiveVisibility.INVISIBLE))

withSpinnerText ()

withSpinnerText () akzeptiert ein Argument vom Typ Matcher <String> und das Argument verweist auf den Wert der aktuell ausgewählten Ansicht des Spinners. Es wird ein Matcher zurückgegeben, der dem Spinner basierend auf dem toString-Wert des ausgewählten Elements entspricht. Der Beispielcode lautet wie folgt:

onView(withSpinnerText(endsWith("USA")))

Es akzeptiert auch das String-Argument oder die Ressourcen-ID des Strings. Der Beispielcode lautet wie folgt:

onView(withResourceName("USA"))
onView(withResourceName(R.string.res_usa))

withSubstring ()

withSubString () ähnelt withText () , hilft jedoch beim Testen der Teilzeichenfolge des Textwerts der Ansicht.

onView(withSubString("Hello"))

hasLinks ()

hasLinks () hat keine Argumente und gibt einen Matcher zurück, der der Ansicht mit Links entspricht. Dies gilt nur für TextView. Der Beispielcode lautet wie folgt:

onView(allOf(withSubString("Hello"), hasLinks()))

Hier ist allOf ein Hamcrest-Matcher. allOf gibt einen Matcher zurück, der mit allen übergebenen Matchern übereinstimmt. Hier wird er verwendet, um eine Ansicht abzugleichen und zu überprüfen, ob der Text der Ansicht Links enthält.

hasTextColor ()

hasTextColor () akzeptiert ein einzelnes Argument vom Typ int und das Argument verweist auf die Ressourcen-ID der Farbe. Es wird ein Matcher zurückgegeben, der anhand seiner Farbe mit der Textansicht übereinstimmt . Dies gilt nur für TextView . Der Beispielcode lautet wie folgt:

onView(allOf(withSubString("Hello"), hasTextColor(R.color.Red)))

hasEllipsizedText ()

hasEllipsizedText () hat kein Argument. Es wird ein Matcher zurückgegeben, der mit der Textansicht übereinstimmt, die Langtext enthält und entweder ellipsiert (zuerst .. zehn .. zuletzt) ​​oder abgeschnitten (zuerst…) ist. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_text_view_id), hasEllipsizedText()))

hasMultilineText ()

hasMultilineText () hat kein Argument. Es wird ein Matcher zurückgegeben, der mit der Textansicht übereinstimmt, die einen mehrzeiligen Text enthält. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_test_view_id), hasMultilineText()))

hasBackground ()

hasBackground () akzeptiert ein einzelnes Argument vom Typ int und das Argument verweist auf die Ressourcen-ID der Hintergrundressource. Es wird ein Matcher zurückgegeben, der anhand seiner Hintergrundressourcen mit der Ansicht übereinstimmt. Der Beispielcode lautet wie folgt:

onView(allOf(withId("image"), hasBackground(R.drawable.your_drawable)))

hasErrorText ()

hasErrorText () akzeptiert ein Argument vom Typ Matcher <String> und das Argument verweist auf den Fehlerzeichenfolgenwert der Ansicht (EditText). Es wird ein Matcher zurückgegeben, der der Ansicht mithilfe der Fehlerzeichenfolge der Ansicht entspricht. Dies gilt nur für EditText . Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.editText_name), hasErrorText(is("name is required"))))

Es akzeptiert auch String-Argumente. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.editText_name), hasErrorText("name is required")))

hasImeAction ()

hasImeAction () akzeptiert ein Argument vom Typ Matcher <Integer> und das Argument verweist auf die von der Ansicht (EditText) unterstützten Eingabemethoden. Es wird ein Matcher zurückgegeben, der mit der unterstützten Eingabemethode der Ansicht mit der Ansicht übereinstimmt. Dies gilt nur für EditText . Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.editText_name),
hasImeAction(is(EditorInfo.IME_ACTION_GO))))

Hier ist EditorInfo.IME_ACTION_GO eine der Eingabemethodenoptionen. hasImeAction () akzeptiert auch ganzzahlige Argumente. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.editText_name),
hasImeAction(EditorInfo.IME_ACTION_GO)))

unterstütztInputMethods ()

unterstütztInputMethods () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, wenn Eingabemethoden unterstützt werden. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.editText_name), supportsInputMethods()))

isRoot ()

isRoot () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Stammansicht entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_root_id), isRoot()))

wird angezeigt()

isDisplayed () hat kein Argument. Es wird ein Matcher zurückgegeben, der der aktuell angezeigten Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isDisplayed()))

isDisplayingAtLeast ()

isDisplayingAtLeast () akzeptiert ein einzelnes Argument vom Typ int. Es wird ein Matcher zurückgegeben, der mit der aktuell angezeigten Ansicht mindestens dem angegebenen Prozentsatz entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isDisplayingAtLeast(75)))

isCompletelyDisplayed ()

isCompletelyDisplayed () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, die derzeit vollständig auf dem Bildschirm angezeigt wird. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isCompletelyDisplayed()))

aktiviert()

isEnabled () hat kein Argument. Es wird ein Matcher zurückgegeben, der der aktivierten Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isEnabled()))

isFocusable ()

isFocusable () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Ansicht mit der Fokusoption entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isFocusable()))

hasFocus ()

hasFocus () hat kein Argument. Es wird ein Matcher zurückgegeben, der der aktuell fokussierten Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), hasFocus()))

isClickable ()

isClickable () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, bei der es sich um eine Klickoption handelt. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isClickable()))

ist ausgewählt()

isSelected () hat kein Argument. Es wird ein Matcher zurückgegeben, der der aktuell ausgewählten Ansicht entspricht. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isSelected()))

wird geprüft()

isChecked () hat kein Argument. Es wird ein Matcher zurückgegeben, der der Ansicht vom Typ CompoundButton (oder Subtyp davon) entspricht und sich im aktivierten Zustand befindet. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isChecked()))

isNotChecked ()

isNotChecked () ist genau gegenüber von isChecked. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_view_id), isNotChecked()))

isJavascriptEnabled ()

isJavascriptEnabled () hat kein Argument. Es wird ein Matcher zurückgegeben, der mit der WebView übereinstimmt, die JavaScript auswertet. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.my_webview_id), isJavascriptEnabled()))

mit Eltern()

withParent () akzeptiert ein Argument vom Typ Matcher <View>. Das Argument bezieht sich auf eine Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, dass die angegebene Ansicht eine übergeordnete Ansicht ist. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.childView), withParent(withId(R.id.parentView))))

hasSibling ()

hasSibling () akzeptiert ein Argument vom Typ Matcher> View <. Das Argument bezieht sich auf eine Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, dass die übergebene Ansicht eine der Geschwisteransichten ist. Der Beispielcode lautet wie folgt:

onView(hasSibling(withId(R.id.siblingView)))

mit Kind()

withChild () akzeptiert ein Argument vom Typ Matcher <View>. Das Argument bezieht sich auf eine Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, dass die übergebene Ansicht eine untergeordnete Ansicht ist. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.parentView), withChild(withId(R.id.childView))))

hasChildCount ()

hasChildCount () akzeptiert ein Argument vom Typ int. Das Argument bezieht sich auf die untergeordnete Anzahl einer Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, die genau die gleiche Anzahl von untergeordneten Ansichten aufweist, wie im Argument angegeben. Der Beispielcode lautet wie folgt:

onView(hasChildCount(4))

hasMinimumChildCount ()

hasMinimumChildCount () akzeptiert ein Argument vom Typ int. Das Argument bezieht sich auf die untergeordnete Anzahl einer Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, die mindestens die im Argument angegebene Anzahl der untergeordneten Ansichten enthält. Der Beispielcode lautet wie folgt:

onView(hasMinimumChildCount(4))

hasDescendant ()

hasDescendant () akzeptiert ein Argument vom Typ Matcher <View>. Das Argument bezieht sich auf eine Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, dass die übergebene Ansicht eine der untergeordneten Ansichten in der Ansichtshierarchie ist. Der Beispielcode lautet wie folgt:

onView(hasDescendant(withId(R.id.descendantView)))

isDescendantOfA ()

isDescendantOfA () akzeptiert ein Argument vom Typ Matcher <View>. Das Argument bezieht sich auf eine Ansicht. Es wird ein Matcher zurückgegeben, der der Ansicht entspricht, dass die übergebene Ansicht eine der Vorfahrenansichten in der Ansichtshierarchie ist. Der Beispielcode lautet wie folgt:

onView(allOf(withId(R.id.myView), isDescendantOfA(withId(R.id.parentView))))

Espresso bietet verschiedene Optionen zum Erstellen eigener benutzerdefinierter Ansichts-Matcher und basiert auf Hamcrest-Matchern . Custom Matcher ist ein sehr leistungsfähiges Konzept, um das Framework zu erweitern und das Framework nach unserem Geschmack anzupassen. Einige der Vorteile des Schreibens von benutzerdefinierten Matchern sind folgende:

  • Um die einzigartige Funktion unserer eigenen benutzerdefinierten Ansichten zu nutzen

  • Benutzerdefinierter Matcher hilft in den AdapterView- basierten Testfällen, mit dem unterschiedlichen Typ der zugrunde liegenden Daten übereinzustimmen .

  • Vereinfachung der aktuellen Matcher durch Kombination der Funktionen mehrerer Matcher

Wir können neue Matcher erstellen, sobald die Nachfrage steigt und es ganz einfach ist. Lassen Sie uns einen neuen benutzerdefinierten Matcher erstellen, der einen Matcher zurückgibt, um sowohl die ID als auch den Text einer Textansicht zu testen .

Espresso bietet die folgenden zwei Klassen, um neue Matcher zu schreiben:

  • TypeSafeMatcher

  • BoundedMatcher

Beide Klassen sind ähnlicher Natur, mit der Ausnahme, dass der BoundedMatcher die Umwandlung des Objekts in den richtigen Typ transparent handhabt, ohne manuell nach dem richtigen Typ zu suchen . Wir werden einen neuen Matcher mit IdAndText unter Verwendung der BoundedMatcher- Klasse erstellen . Lassen Sie uns die Schritte überprüfen, um neue Matcher zu schreiben.

  • Fügen Sie die folgende Abhängigkeit in die Datei app / build.gradle ein und synchronisieren Sie sie.

dependencies {
   implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Erstellen Sie eine neue Klasse mit unseren Matchern (Methoden) und markieren Sie sie als endgültig

public final class MyMatchers {
}
  • Deklarieren Sie eine statische Methode innerhalb der neuen Klasse mit den erforderlichen Argumenten und legen Sie Matcher <Ansicht> als Rückgabetyp fest.

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
   }
}
  • Erstellen Sie ein neues BoundedMatcher-Objekt (auch Rückgabewert) mit der folgenden Signatur in der statischen Methode.

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
      };
   }
}
  • Außer Kraft setzen describeTo und matchesSafely Methoden im BoundedMatcher Objekt. descriptionTo hat ein einzelnes Argument vom Typ Description ohne Rückgabetyp und wird verwendet, um Informationen zu Matchern zu fehlerhaft zu machen. matchSafely hat ein einzelnes Argument vom Typ TextView mit dem Rückgabetyp boolean und wird verwendet, um mit der Ansicht übereinzustimmen .

Die endgültige Version des Codes lautet wie folgt:

public final class MyMatchers {
   @NonNull
   public static Matcher<View> withIdAndText(final Matcher<Integer>
   integerMatcher, final Matcher<String> stringMatcher) {
      return new BoundedMatcher<View, TextView>(TextView.class) {
         @Override
         public void describeTo(final Description description) {
            description.appendText("error text: ");
            stringMatcher.describeTo(description);
            integerMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(final TextView textView) {
            return stringMatcher.matches(textView.getText().toString()) &&
            integerMatcher.matches(textView.getId());
         }
      };
   }
}
  • Schließlich können wir unseren Mew-Matcher verwenden, um den Testfall wie unten beschrieben zu schreiben:

@Test
public void view_customMatcher_isCorrect() {
   onView(withIdAndText(is((Integer) R.id.textView_hello), is((String) "Hello World!")))
      .check(matches(withText("Hello World!")));
}

Wie bereits erwähnt, wird die Ansichtszusicherung verwendet, um zu bestätigen, dass sowohl die tatsächliche Ansicht (mithilfe von Ansichtsabgleichern gefunden) als auch die erwarteten Ansichten identisch sind. Ein Beispielcode lautet wie folgt:

onView(withId(R.id.my_view)) .check(matches(withText("Hello")))

Hier,

  • onView () gibt das ViewInteration- Objekt zurück, das der übereinstimmenden Ansicht entspricht. ViewInteraction wird verwendet, um mit der übereinstimmenden Ansicht zu interagieren.

  • withId (R.id.my_view) gibt einen Ansichts-Matcher zurück, der mit der Ansicht (tatsächlich) übereinstimmt , deren ID- Attribute my_view entsprechen .

  • withText ( „Hallo“) auch einen Blick Matcher zurückgibt , die mit der Ansicht übereinstimmen (erwartet) mit Textattributen gleich zu Hallo .

  • check ist eine Methode, die ein Argument vom Typ ViewAssertion akzeptiert und eine Assertion mit dem im ViewAssertion- Objekt übergebenen Objekt ausführt .

  • Übereinstimmungen (withText ("Hallo")) gibt eine Ansichtszusicherung zurück, die das ausführtreal jobzu behaupten, dass sowohl die tatsächliche Ansicht (gefunden mit withId ) als auch die erwartete Ansicht (gefunden mit withText ) ein und dieselbe sind.

Lassen Sie uns einige der Methoden lernen, die vom Espresso-Test-Framework bereitgestellt werden, um Ansichtsobjekte zu bestätigen.

ist nicht vorhanden()

Gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der Ansichtsabgleich keine übereinstimmende Ansicht findet.

onView(withText("Hello")) .check(doesNotExist());

Hier stellt der Testfall sicher, dass es keine Ansicht mit Text Hallo gibt.

Streichhölzer()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der Ansichts-Matcher (tatsächlich) vorhanden ist und mit der vom Zielansichts-Matcher übereinstimmenden Ansicht übereinstimmt.

onView(withId(R.id.textView_hello)) .check(matches(withText("Hello World!")));

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.textView_hello vorhanden ist und mit der Zielansicht mit dem Text Hello World!

isBottomAlignedWith ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und unten am Zielansichts-Matcher ausgerichtet ist.

onView(withId(R.id.view)) .check(isBottomAlignedWith(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und unten mit der Ansicht mit der ID R.id.target_view ausgerichtet ist .

isCompletelyAbove ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und vollständig über dem Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isCompletelyAbove(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und vollständig über der Ansicht mit der ID R.id.target_view positioniert ist

isCompletelyBelow ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und vollständig unter dem Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isCompletelyBelow(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und vollständig unter der Ansicht mit der ID R.id.target_view positioniert ist .

isCompletelyLeftOf ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und vollständig links vom Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isCompletelyLeftOf(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und vollständig links von der Ansicht mit der ID R.id.target_view positioniert ist

isCompletelyRightOf ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und vollständig rechts vom Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isCompletelyRightOf(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und vollständig rechts von der Ansicht mit der ID R.id.target_view positioniert ist.

isLeftAlignedWith ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und links vom Zielansichts-Matcher ausgerichtet bleibt.

onView(withId(R.id.view)) .check(isLeftAlignedWith(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und links mit der Ansicht mit der ID R.id.target_view ausgerichtet ist

isPartiallyAbove ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und teilweise über dem Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isPartiallyAbove(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und teilweise über der Ansicht mit der ID R.id.target_view positioniert ist

isPartiallyBelow ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und teilweise unter dem Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isPartiallyBelow(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und teilweise unter der Ansicht mit der ID R.id.target_view positioniert ist .

isPartiallyLeftOf ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und teilweise links vom Zielansichts-Matcher positioniert ist.

onView(withId(R.id.view)) .check(isPartiallyLeftOf(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und teilweise links von der Ansicht mit der ID R.id.target_view positioniert ist .

isPartiallyRightOf ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und teilweise rechts vom Zielansichts-Matcher positioniert ist

onView(withId(R.id.view)) .check(isPartiallyRightOf(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und teilweise rechts von der Ansicht mit der ID R.id.target_view positioniert ist .

isRightAlignedWith ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und direkt mit dem Zielansichts-Matcher ausgerichtet ist.

onView(withId(R.id.view)) .check(isRightAlignedWith(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und rechts mit der Ansicht mit der ID R.id.target_view ausgerichtet ist .

isTopAlignedWith ()

Akzeptiert einen Zielansichts-Matcher und gibt eine Ansichtszusicherung zurück, die sicherstellt, dass der (tatsächliche) Ansichts-Matcher vorhanden ist und oben mit dem Zielansichts-Matcher ausgerichtet ist.

onView(withId(R.id.view)) .check(isTopAlignedWith(withId(R.id.target_view)))

Hier stellt der Testfall sicher, dass die Ansicht mit der ID R.id.view vorhanden ist und oben mit der Ansicht mit der ID R.id.target_view ausgerichtet ist

noEllipsizedText ()

Gibt eine Ansichtszusicherung zurück, die sicherstellt, dass die Ansichtshierarchie keine ellipsierten oder abgeschnittenen Textansichten enthält.

onView(withId(R.id.view)) .check(noEllipsizedText());

noMultilineButtons ()

Gibt eine Ansichtszusicherung zurück, die sicherstellt, dass die Ansichtshierarchie keine mehrzeiligen Schaltflächen enthält.

onView(withId(R.id.view)) .check(noMultilineButtons());

noOverlaps ()

Gibt eine Ansichtszusicherung zurück, die sicherstellt, dass sich das TextView oder ImageView zugeordnete Nachkommenobjekt nicht überlappt. Es gibt eine weitere Option, die einen Zielansichts-Matcher akzeptiert und eine Ansichtszusicherung zurückgibt, die sicherstellt, dass sich die mit der Zielansicht übereinstimmende Nachkommenansicht nicht überlappt.

Wie bereits erwähnt, automatisieren Ansichtsaktionen alle möglichen Aktionen, die von Benutzern in einer Android-Anwendung ausgeführt werden können. Espresso onView und "onData" stellen die perform- Methode bereit , die Ansichtsaktionen akzeptiert und die entsprechenden Benutzeraktionen in der Testumgebung aufruft / automatisiert. Beispielsweise ist "click ()" eine Ansichtsaktion, die bei Übergabe an die Methode onView ( R.id.myButton ) .perform (click ()) das Klickereignis der Schaltfläche auslöst (mit der ID: "myButton"). ) in der Testumgebung.

In diesem Kapitel erfahren Sie mehr über die Ansichtsaktionen, die vom Espresso-Test-Framework bereitgestellt werden.

Text eingeben()

typeText () akzeptiert ein Argument (Text) vom Typ String und gibt eine Ansichtsaktion zurück. Die zurückgegebene Ansichtsaktion gibt den bereitgestellten Text in die Ansicht ein. Vor dem Platzieren des Textes wird einmal auf die Ansicht getippt. Der Inhalt kann an einer beliebigen Stelle platziert werden, wenn er bereits Text enthält.

onView(withId(R.id.text_view)).perform(typeText("Hello World!"))

typeTextIntoFocusedView ()

typeTextIntoFocusedView () ähnelt typeText (), außer dass der Text direkt neben der Cursorposition in der Ansicht platziert wird.

onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))

replaceText ()

replaceText () ähnelt typeText () , ersetzt jedoch den Inhalt der Ansicht.

onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))

Klartext()

clearText () hat keine Argumente und gibt eine Ansichtsaktion zurück, mit der der Text in der Ansicht gelöscht wird.

onView(withId(R.id.text_view)).perform(clearText())

drücken Sie die Taste()

pressKey () akzeptiert den Schlüsselcode (z. B. KeyEvent.KEYCODE_ENTER) und gibt eine Ansichtsaktion zurück, bei der die Taste gedrückt wird, die dem Schlüsselcode entspricht.

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", pressKey(KeyEvent.KEYCODE_ENTER))

pressMenuKey ()

pressMenuKey () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Hardware- Menütaste drückt.

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", pressKey(KeyEvent.KEYCODE_ENTER), pressMenuKey())

closeSoftKeyboard ()

closeSoftKeyboard () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Tastatur schließt, wenn eine geöffnet wird.

onView(withId(R.id.text_view)).perform(typeText(
   "Hello World!", closeSoftKeyboard())

klicken()

click () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Klickaktion der Ansicht aufruft.

onView(withId(R.id.button)).perform(click())

Doppelklick()

doubleClick () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Doppelklickaktion der Ansicht aufruft.

onView(withId(R.id.button)).perform(doubleClick())

longClick ()

longClick () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Long-Click-Aktion der Ansicht aufruft.

onView(withId(R.id.button)).perform(longClick())

pressBack ()

pressBack () hat keine Argumente und gibt eine Ansichtsaktion zurück, die auf die Schaltfläche "Zurück" klickt.

onView(withId(R.id.button)).perform(pressBack())

pressBackUnconditionally ()

pressBackUnconditionally () hat keine Argumente und gibt eine Ansichtsaktion zurück, die auf die Schaltfläche "Zurück" klickt und keine Ausnahme auslöst, wenn die Aktion "Zurück" die Anwendung selbst beendet.

onView(withId(R.id.button)).perform(pressBack())

Verbindung öffnen()

openLink () hat zwei Argumente. Das erste Argument (Linktext) ist vom Typ Matcher und verweist auf den Text des HTML- Ankertags . Das zweite Argument (URL) ist vom Typ Matcher und verweist auf die URL des HTML- Ankertags . Dies gilt nur für TextView . Es gibt eine Ansichtsaktion zurück, die alle im Inhalt der Textansicht verfügbaren HTML-Ankertags sammelt, das Ankertag findet, das mit dem ersten Argument (Linktext) und dem zweiten Argument (URL) übereinstimmt, und schließlich die entsprechende URL öffnet. Betrachten wir eine Textansicht mit dem Inhalt als -

<a href="http://www.google.com/">copyright</a>

Anschließend kann der Link mit dem folgenden Testfall geöffnet und getestet werden:

onView(withId(R.id.text_view)).perform(openLink(is("copyright"),
   is(Uri.parse("http://www.google.com/"))))

Hier ruft openLink den Inhalt der Textansicht ab, findet den urheberrechtlich geschützten Link als Text, www.google.com als URL und öffnet die URL in einem Browser.

openLinkWithText ()

openLinkWithText () hat ein Argument, das entweder vom Typ ** String * oder vom Typ Matcher sein kann. Es ist einfach eine Abkürzung zur openLink * -Methode.

onView(withId(R.id.text_view)).perform(openLinkWithText("copyright"))

openLinkWithUri ()

openLinkWithUri () hat ein Argument, das entweder vom Typ String oder Matcher sein kann. Es ist einfach ein kurzer Schnitt an den Openlink * Methode.

onView(withId(R.id.text_view)).perform(openLinkWithUri("http://www.google.com/"))

pressImeActionButton ()

pressImeActionButton () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die in der Konfiguration von android: imeOptions festgelegte Aktion ausführt . Wenn beispielsweise android: imeOptions gleich actionNext ist, bewegt sich der Cursor zur nächsten möglichen EditText- Ansicht auf dem Bildschirm.

onView(withId(R.id.text_view)).perform(pressImeActionButton())

scrollTo ()

scrollTo () hat keine Argumente und gibt eine Ansichtsaktion zurück, mit der die übereinstimmende scrollView auf dem Bildschirm gescrollt wird.

onView(withId(R.id.scrollView)).perform(scrollTo())

swipeDown ()

swipeDown () hat keine Argumente und gibt eine Ansichtsaktion zurück, die eine Wischaktion auf dem Bildschirm auslöst .

onView(withId(R.id.root)).perform(swipeDown())

swipeUp ()

swipeUp () hat keine Argumente und gibt eine Ansichtsaktion zurück, die eine Swipe-up-Aktion auf dem Bildschirm auslöst .

onView(withId(R.id.root)).perform(swipeUp())

wische nach rechts()

swipeRight () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Aktion "Nach rechts wischen" auf dem Bildschirm auslöst .

onView(withId(R.id.root)).perform(swipeRight())

nach links wischen()

swipeLeft () hat keine Argumente und gibt eine Ansichtsaktion zurück, die die Aktion "Nach links wischen" auf dem Bildschirm auslöst .

onView(withId(R.id.root)).perform(swipeLeft())

AdapterView ist eine spezielle Art von Ansicht, die speziell zum Rendern einer Sammlung ähnlicher Informationen wie Produktlisten und Benutzerkontakte entwickelt wurde, die mit Adapter aus einer zugrunde liegenden Datenquelle abgerufen wurden . Die Datenquelle kann eine einfache Liste zu komplexen Datenbankeinträgen sein. Einige der von AdapterView abgeleiteten Ansichten sind ListView , GridView und Spinner .

AdapterView rendert die Benutzeroberfläche dynamisch in Abhängigkeit von der in der zugrunde liegenden Datenquelle verfügbaren Datenmenge. Darüber hinaus rendert AdapterView nur die minimal erforderlichen Daten, die im verfügbaren sichtbaren Bereich des Bildschirms gerendert werden können. AdapterView spart Speicherplatz und sorgt dafür, dass die Benutzeroberfläche auch dann reibungslos aussieht, wenn die zugrunde liegenden Daten groß sind.

Bei der Analyse sind die Option onView und ihre Ansichtsabgleiche aufgrund der Art der AdapterView- Architektur irrelevant, da die zu testende Ansicht möglicherweise überhaupt nicht gerendert wird. Glücklicherweise bietet Espresso eine Methode, onData ( ), die Hamcrest-Matcher (relevant für den Datentyp der zugrunde liegenden Daten) akzeptiert, um die zugrunde liegenden Daten abzugleichen, und ein Objekt vom Typ DataInteraction zurückgibt , das der Ansicht der übereinstimmenden Daten entspricht. Ein Beispielcode lautet wie folgt:

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

Hier stimmt onData () mit dem Eintrag "Apple" überein , wenn er in den zugrunde liegenden Daten (Array-Liste) verfügbar ist, und gibt das DataInteraction- Objekt zurück, um mit der übereinstimmenden Ansicht zu interagieren (TextView entspricht dem Eintrag "Apple").

Methoden

DataInteraction bietet die folgenden Methoden zur Interaktion mit der Ansicht:

ausführen()

Dies akzeptiert Ansichtsaktionen und löst die übergebenen Ansichtsaktionen aus.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())

prüfen()

Dies akzeptiert Ansichtszusicherungen und überprüft die übergebenen Ansichtszusicherungen.

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .check(matches(withText("Apple")))

inAdapterView ()

Dies akzeptiert View Matcher. Es wählt die bestimmte AdapterView basierend auf den übergebenen View-Matchern aus und gibt das DataInteraction- Objekt zurück, um mit der übereinstimmenden AdapterView zu interagieren

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

atPosition ()

Dies akzeptiert ein Argument vom Typ Integer und verweist auf die Position des Elements in den zugrunde liegenden Daten. Es wählt die Ansicht aus, die dem übergebenen Positionswert der Daten entspricht, und gibt das DataInteraction- Objekt zurück, um mit der übereinstimmenden Ansicht zu interagieren. Es ist nützlich, wenn wir die richtige Reihenfolge der zugrunde liegenden Daten kennen.

onData(allOf())
   .inAdapterView(withId(R.id.adapter_view))
   .atPosition(5)
   .perform(click())

onChildView ()

Dies akzeptiert Ansichtsabgleiche und stimmt mit der Ansicht innerhalb der spezifischen untergeordneten Ansicht überein. Beispielsweise können wir mit bestimmten Elementen wie der Schaltfläche " Kaufen" in einer auf Produktlisten basierenden AdapterView interagieren .

onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
   .onChildView(withId(R.id.buy_button))
   .perform(click())

Schreiben Sie eine Beispielanwendung

Führen Sie die folgenden Schritte aus, um eine einfache Anwendung auf Basis von AdapterView zu schreiben und einen Testfall mit der Methode onData () zu schreiben .

  • Starten Sie Android Studio.

  • Erstellen Sie ein neues Projekt wie zuvor beschrieben und nennen Sie es MyFruitApp .

  • Migrieren Sie die Anwendung auf AndroidX Framework UmgestaltenMigrate zu AndroidX Optionsmenü.

  • Entfernen Sie das Standarddesign in der Hauptaktivität und fügen Sie ListView hinzu . Der Inhalt der Datei activity_main.xml lautet wie folgt:

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content" />
</RelativeLayout>
  • Fügen Sie die neue Layoutressource item.xml hinzu , um die Elementvorlage der Listenansicht anzugeben. Der Inhalt der item.xml lautet wie folgt:

<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • Erstellen Sie nun einen Adapter mit einem Fruchtarray als zugrunde liegenden Daten und setzen Sie ihn auf die Listenansicht. Dies muss in onCreate () von MainActivity erfolgen, wie unten angegeben.

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   
   // Find fruit list view
   final ListView listView = (ListView) findViewById(R.id.listView);
   
   // Initialize fruit data
   String[] fruits = new String[]{
      "Apple", 
      "Banana", 
      "Cherry", 
      "Dates", 
      "Elderberry", 
      "Fig", 
      "Grapes", 
      "Grapefruit", 
      "Guava",
      "Jack fruit", 
      "Lemon",
      "Mango", 
      "Orange", 
      "Papaya", 
      "Pears", 
      "Peaches", 
      "Pineapple",
      "Plums", 
      "Raspberry",
      "Strawberry", 
      "Watermelon"
   };
   
   // Create array list of fruits
   final ArrayList<String> fruitList = new ArrayList<String>();
   for (int i = 0; i < fruits.length; ++i) {
      fruitList.add(fruits[i]);
   }
   
   // Create Array adapter
   final ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item, fruitList);
   
   // Set adapter in list view
   listView.setAdapter(adapter);
}
  • Kompilieren Sie nun den Code und führen Sie die Anwendung aus. Der Screenshot der My Fruit App sieht wie folgt aus:

  • Öffnen Sie nun die Datei ExampleInstrumentedTest.java und fügen Sie ActivityTestRule wie unten angegeben hinzu.

@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class);

Stellen Sie außerdem sicher, dass die Testkonfiguration in app / build.gradle - durchgeführt wird.

dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
  • Fügen Sie einen neuen Testfall hinzu, um die Listenansicht wie folgt zu testen:

@Test
public void listView_isCorrect() {
   // check list view is visible
   onView(withId(R.id.listView)).check(matches(isDisplayed()));
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click());
   onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
      .check(matches(withText("Apple")));
   // click a child item
   onData(allOf())
      .inAdapterView(withId(R.id.listView))
      .atPosition(10)
      .perform(click());
}
  • Führen Sie den Testfall schließlich über das Kontextmenü von Android Studio aus und überprüfen Sie, ob alle Testfälle erfolgreich sind.

WebView ist eine spezielle Ansicht von Android zum Anzeigen von Webseiten in der Anwendung. WebView bietet nicht alle Funktionen einer vollwertigen Browseranwendung wie Chrome und Firefox. Es bietet jedoch die vollständige Kontrolle über den anzuzeigenden Inhalt und macht alle Android-Funktionen verfügbar, die auf den Webseiten aufgerufen werden sollen. Es aktiviert WebView und bietet eine spezielle Umgebung, in der die Benutzeroberfläche mithilfe der HTML-Technologie und nativer Funktionen wie Kamera und Wählen eines Kontakts einfach gestaltet werden kann. Mit diesem Funktionsumfang kann ein WebView eine neue Art von Anwendung namens Hybridanwendung bereitstellen , bei der die Benutzeroberfläche in HTML und die Geschäftslogik entweder in JavaScript oder über einen externen API-Endpunkt erfolgt.

Normalerweise muss das Testen einer WebView eine Herausforderung sein, da sie HTML-Technologie für ihre Benutzeroberflächenelemente anstelle nativer Benutzeroberflächen / Ansichten verwendet. Espresso zeichnet sich in diesem Bereich durch die Bereitstellung eines neuen Satzes für Web-Matcher und Web-Assertions aus, der absichtlich nativen View-Matchern und View-Assertions ähnelt. Gleichzeitig bietet es einen ausgewogenen Ansatz, indem es auch eine auf Webtechnologie basierende Testumgebung einbezieht.

Espresso Web basiert auf dem WebDriver Atom-Framework, mit dem Webelemente gefunden und bearbeitet werden. Atom ähnelt dem Anzeigen von Aktionen. Atom übernimmt die gesamte Interaktion innerhalb einer Webseite. WebDriver stellt eine vordefinierte Reihe von Methoden wie findElement () und getElement () zur Verfügung, um Webelemente zu finden, und gibt die entsprechenden Atome zurück (um Aktionen auf der Webseite auszuführen).

Eine Standard-Webtestanweisung sieht wie folgt aus:

onWebView()
   .withElement(Atom)
   .perform(Atom)
   .check(WebAssertion)

Hier,

  • onWebView () - Ähnlich wie bei onView () wird eine Reihe von APIs zum Testen einer WebView verfügbar gemacht.

  • withElement () - Eine der verschiedenen Methoden zum Suchen von Webelementen innerhalb einer Webseite mithilfe von Atom und zum Zurückgeben des WebInteration-Objekts, das ViewInteraction ähnelt.

  • perform () - Führt die Aktion innerhalb einer Webseite mit Atom aus und gibt WebInteraction zurück.

  • check () - Dies führt die erforderliche Zusicherung mit WebAssertion durch.

Ein Beispiel für einen Web-Testcode lautet wie folgt:

onWebView()
   .withElement(findElement(Locator.ID, "apple"))
   .check(webMatches(getText(), containsString("Apple")))

Hier,

  • findElement () sucht ein Element und gibt ein Atom zurück

  • webMatches ähnelt der Matches-Methode

Schreiben Sie eine Beispielanwendung

Lassen Sie uns eine einfache Anwendung schreiben, die auf WebView basiert, und einen Testfall mit der Methode onWebView () schreiben . Befolgen Sie diese Schritte, um eine Beispielanwendung zu schreiben -

  • Starten Sie Android Studio.

  • Erstellen Sie ein neues Projekt wie zuvor beschrieben und nennen Sie es MyWebViewApp .

  • Migrieren Sie die Anwendung auf AndroidX Framework UmgestaltenMigrate zu AndroidX Optionsmenü.

  • Fügen Sie die folgende Konfigurationsoption in die Datei AndroidManifest.xml ein , um die Berechtigung zum Zugriff auf das Internet zu erteilen.

<uses-permission android:name = "android.permission.INTERNET" />
  • Espresso Web wird als separates Plugin bereitgestellt. Fügen Sie also die Abhängigkeit in der app / build.gradle hinzu und synchronisieren Sie sie.

dependencies {
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.1'
}
  • Entfernen Sie das Standarddesign in der Hauptaktivität und fügen Sie WebView hinzu. Der Inhalt der Datei activity_main.xml lautet wie folgt:

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <WebView
      android:id = "@+id/web_view_test"
      android:layout_width = "fill_parent"
      android:layout_height = "fill_parent" />
</RelativeLayout>
  • Erstellen Sie eine neue Klasse, ExtendedWebViewClient , die WebViewClient erweitert , und überschreiben Sie die Methode shouldOverrideUrlLoading , um die Verknüpfungsaktion in dieselbe WebView zu laden . Andernfalls wird ein neues Browserfenster außerhalb der Anwendung geöffnet. Platzieren Sie es in MainActivity.java .

private class ExtendedWebViewClient extends WebViewClient {
   @Override
   public boolean shouldOverrideUrlLoading(WebView view, String url) {
      view.loadUrl(url);
      return true;
   }
}
  • Fügen Sie nun den folgenden Code in die onCreate-Methode von MainActivity ein . Der Zweck des Codes besteht darin, das WebView zu finden , es ordnungsgemäß zu konfigurieren und schließlich die Ziel-URL zu laden.

// Find web view
WebView webView = (WebView) findViewById(R.id.web_view_test);

// set web view client
webView.setWebViewClient(new ExtendedWebViewClient());

// Clear cache
webView.clearCache(true);

// load Url
webView.loadUrl("http://<your domain or IP>/index.html");

Hier,

  • Der Inhalt von index.html lautet wie folgt:

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   <body>
      <h1>Fruits</h1>
      <ol>
         <li><a href = "apple.html" id = "apple">Apple</a></li>
         <li><a href = "banana.html" id = "banana">Banana</a></li>
         </ol>
   </body>
</html>
  • Der Inhalt der Datei " apple.html" , auf die in " index.html" verwiesen wird, lautet wie folgt:

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   
   <body>
      <h1>Apple</h1>
   </body>
</html>
  • Der Inhalt der Datei banana.html , auf die in banana.html verwiesen wird, lautet wie folgt:

<html>
   <head>
      <title>Android Web View Sample</title>
   </head>
   
   <body>
      <h1>Banana</h1>
   </body>
</html>
  • Platzieren Sie index.html, apple.html und banana.html auf einem Webserver

  • Ersetzen Sie die URL in der loadUrl-Methode durch Ihre konfigurierte URL.

  • Führen Sie nun die Anwendung aus und überprüfen Sie manuell, ob alles in Ordnung ist. Unten sehen Sie den Screenshot der WebView-Beispielanwendung -

  • Öffnen Sie nun die Datei ExampleInstrumentedTest.java und fügen Sie die folgende Regel hinzu:

@Rule
public ActivityTestRule<MainActivity> mActivityRule =
   new ActivityTestRule<MainActivity>(MainActivity.class, false, true) {
   @Override
   protected void afterActivityLaunched() {
      onWebView(withId(R.id.web_view_test)).forceJavascriptEnabled();
   }
};

Hier haben wir das WebView gefunden und JavaScript des WebView aktiviert, da das Espresso- Webtest- Framework ausschließlich über die JavaScript-Engine zum Identifizieren und Bearbeiten von Webelementen funktioniert.

  • Fügen Sie nun den Testfall hinzu, um unser WebView und sein Verhalten zu testen .

@Test
public void webViewTest(){
   onWebView()
      .withElement(findElement(Locator.ID, "apple"))
      .check(webMatches(getText(), containsString("Apple")))
      .perform(webClick())
      .withElement(findElement(Locator.TAG_NAME, "h1"))
      .check(webMatches(getText(), containsString("Apple")));
}

Hier wurde der Test in der folgenden Reihenfolge durchgeführt:

  • fand den Link, Apple mit seinem ID-Attribut über die findElement () -Methode und die Locator.ID- Enumeration.

  • Überprüft den Text des Links mit der webMatches () -Methode

  • führt eine Klickaktion für den Link aus. Es öffnet sich die Seite apple.html .

  • Das Element h1 wurde erneut mit den Methoden findElement () und der Aufzählung Locator.TAG_NAME gefunden .

  • Schließlich wird der Text des h1- Tags erneut mit der webMatches () -Methode überprüft .

  • Führen Sie den Testfall schließlich über das Kontextmenü von Android Studio aus.

In diesem Kapitel erfahren Sie, wie Sie asynchrone Vorgänge mit Espresso Idling Resources testen.

Eine der Herausforderungen der modernen Anwendung besteht darin, eine reibungslose Benutzererfahrung zu bieten. Die Bereitstellung einer reibungslosen Benutzererfahrung erfordert viel Arbeit im Hintergrund, um sicherzustellen, dass der Bewerbungsprozess nicht länger als einige Millisekunden dauert. Die Hintergrundaufgabe reicht von der einfachen bis zur kostspieligen und komplexen Aufgabe, Daten von der Remote-API / Datenbank abzurufen. Um der Herausforderung in der Vergangenheit zu begegnen, hat ein Entwickler kostspielige und lang laufende Aufgaben in einen Hintergrundthread geschrieben und nach Abschluss des Hintergrundthreads mit dem Haupt- UIThread synchronisiert .

Wenn die Entwicklung einer Multithread-Anwendung komplex ist, ist das Schreiben von Testfällen noch komplexer. Beispielsweise sollten wir eine AdapterView nicht testen, bevor die erforderlichen Daten aus der Datenbank geladen wurden. Wenn das Abrufen der Daten in einem separaten Thread erfolgt, muss der Test warten, bis der Thread abgeschlossen ist. Daher sollte die Testumgebung zwischen Hintergrundthread und UI-Thread synchronisiert werden. Espresso bietet eine hervorragende Unterstützung beim Testen der Multithread-Anwendung. Eine Anwendung verwendet Thread auf folgende Weise und Espresso unterstützt jedes Szenario.

Threading der Benutzeroberfläche

Es wird intern vom Android SDK verwendet, um eine reibungslose Benutzererfahrung mit komplexen UI-Elementen zu ermöglichen. Espresso unterstützt dieses Szenario transparent und benötigt keine Konfiguration und spezielle Codierung.

Asynchrone Aufgabe

Moderne Programmiersprachen unterstützen die asynchrone Programmierung für leichtes Threading ohne die Komplexität der Thread-Programmierung. Die asynchrone Aufgabe wird auch transparent vom Espresso-Framework unterstützt.

Benutzer-Thread

Ein Entwickler kann einen neuen Thread starten, um komplexe oder große Datenmengen aus der Datenbank abzurufen. Um dieses Szenario zu unterstützen, bietet Espresso ein Konzept für Leerlaufressourcen.

In diesem Kapitel lernen Sie das Konzept des Leerlaufs von Ressourcen und dessen Vorgehensweise kennen.

Überblick

Das Konzept der Leerlaufressource ist sehr einfach und intuitiv. Die Grundidee besteht darin, eine Variable (boolescher Wert) zu erstellen, wenn ein Prozess mit langer Laufzeit in einem separaten Thread gestartet wird, um festzustellen, ob der Prozess ausgeführt wird oder nicht, und ihn in der Testumgebung zu registrieren. Während des Tests überprüft der Testläufer die registrierte Variable, falls vorhanden, und ermittelt dann ihren Betriebsstatus. Wenn der Laufstatus wahr ist, wartet der Testläufer, bis der Status falsch wird.

Espresso bietet eine Schnittstelle, IdlingResources, um den Betriebsstatus aufrechtzuerhalten. Die Hauptmethode zur Implementierung ist isIdleNow (). Wenn isIdleNow () true zurückgibt, setzt espresso den Testvorgang fort oder wartet, bis isIdleNow () false zurückgibt. Wir müssen IdlingResources implementieren und die abgeleitete Klasse verwenden. Espresso bietet auch einige der integrierten IdlingResources-Implementierungen, um unsere Arbeitsbelastung zu verringern. Sie sind wie folgt,

CountingIdlingResource

Dadurch wird ein interner Zähler für die laufende Aufgabe verwaltet. Es macht die Methoden increment () und decrement () verfügbar. increment () fügt dem Zähler einen hinzu und decrement () entfernt einen vom Zähler. isIdleNow () gibt nur dann true zurück, wenn keine Aufgabe aktiv ist.

UriIdlingResource

Dies ähnelt CounintIdlingResource, außer dass der Zähler für einen längeren Zeitraum Null sein muss, um auch die Netzwerklatenz zu berücksichtigen.

IdlingThreadPoolExecutor

Dies ist eine benutzerdefinierte Implementierung von ThreadPoolExecutor , um die Anzahl der aktiven laufenden Aufgaben im aktuellen Thread-Pool beizubehalten.

IdlingScheduledThreadPoolExecutor

Dies ähnelt IdlingThreadPoolExecutor , plant jedoch auch eine Aufgabe und eine benutzerdefinierte Implementierung von ScheduledThreadPoolExecutor.

Wenn eine der oben genannten oder eine benutzerdefinierte Implementierung von IdlingResources in der Anwendung verwendet wird, müssen wir sie ebenfalls in der Testumgebung registrieren, bevor wir die Anwendung mit der folgenden IdlingRegistry- Klasse testen können.

IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());

Darüber hinaus kann es nach Abschluss des Tests wie folgt entfernt werden:

IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());

Espresso bietet diese Funktionalität in einem separaten Paket, und das Paket muss wie folgt in der app.gradle konfiguriert werden.

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}

Beispielanwendung

Lassen Sie uns eine einfache Anwendung erstellen, um die Früchte aufzulisten, indem wir sie von einem Webdienst in einem separaten Thread abrufen und sie dann mithilfe des Ressourcenkonzepts im Leerlauf testen.

  • Starten Sie Android Studio.

  • Erstellen Sie ein neues Projekt wie zuvor beschrieben und nennen Sie es MyIdlingFruitApp

  • Migrieren Sie die Anwendung über das Optionsmenü Refactor → Auf AndroidX migrieren auf das AndroidX-Framework .

  • Fügen Sie die Espresso-Ressourcenbibliothek im Leerlauf in die app / build.gradle ein (und synchronisieren Sie sie) wie unten angegeben.

dependencies {
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • Entfernen Sie das Standarddesign in der Hauptaktivität und fügen Sie ListView hinzu. Der Inhalt der Datei activity_main.xml lautet wie folgt:

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <ListView
      android:id = "@+id/listView"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content" />
</RelativeLayout>
  • Fügen Sie die neue Layoutressource item.xml hinzu , um die Elementvorlage der Listenansicht anzugeben. Der Inhalt der item.xml lautet wie folgt:

<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
   android:id = "@+id/name"
   android:layout_width = "fill_parent"
   android:layout_height = "fill_parent"
   android:padding = "8dp"
/>
  • Erstellen Sie eine neue Klasse - MyIdlingResource . MyIdlingResource wird verwendet, um unsere IdlingResource an einem Ort zu halten und bei Bedarf abzurufen. In unserem Beispiel werden wir CountingIdlingResource verwenden .

package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.idling.CountingIdlingResource;

public class MyIdlingResource {
   private static CountingIdlingResource mCountingIdlingResource =
      new CountingIdlingResource("my_idling_resource");
   public static void increment() {
      mCountingIdlingResource.increment();
   }
   public static void decrement() {
      mCountingIdlingResource.decrement();
   }
   public static IdlingResource getIdlingResource() {
      return mCountingIdlingResource;
   }
}
  • Deklarieren Sie eine globale Variable, mIdlingResource vom Typ CountingIdlingResource, in der MainActivity- Klasse wie folgt :

@Nullable
private CountingIdlingResource mIdlingResource = null;
  • Schreiben Sie eine private Methode, um die Obstliste wie folgt aus dem Internet abzurufen.

private ArrayList<String> getFruitList(String data) {
   ArrayList<String> fruits = new ArrayList<String>();
   try {
      // Get url from async task and set it into a local variable
      URL url = new URL(data);
      Log.e("URL", url.toString());
      
      // Create new HTTP connection
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      
      // Set HTTP connection method as "Get"
      conn.setRequestMethod("GET");
      
      // Do a http request and get the response code
      int responseCode = conn.getResponseCode();
      
      // check the response code and if success, get response content
      if (responseCode == HttpURLConnection.HTTP_OK) {
         BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
         String line;
         StringBuffer response = new StringBuffer();
         while ((line = in.readLine()) != null) {
            response.append(line);
         }
         in.close();
         JSONArray jsonArray = new JSONArray(response.toString());
         Log.e("HTTPResponse", response.toString());
         for(int i = 0; i < jsonArray.length(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String name = String.valueOf(jsonObject.getString("name"));
            fruits.add(name);
         }
      } else {
         throw new IOException("Unable to fetch data from url");
      }
      conn.disconnect();
   } catch (IOException | JSONException e) {
      e.printStackTrace();
   }
   return fruits;
}
  • Erstellen Sie eine neue Aufgabe in der onCreate () -Methode, um die Daten mit unserer getFruitList- Methode aus dem Web abzurufen. Anschließend erstellen Sie einen neuen Adapter und legen ihn für die Listenansicht fest . Verringern Sie außerdem die Leerlaufressource, sobald unsere Arbeit im Thread abgeschlossen ist. Der Code lautet wie folgt:

// Get data
class FruitTask implements Runnable {
   ListView listView;
   CountingIdlingResource idlingResource;
   FruitTask(CountingIdlingResource idlingRes, ListView listView) {
      this.listView = listView;
      this.idlingResource = idlingRes;
   }
   public void run() {
      //code to do the HTTP request
      final ArrayList<String> fruitList = getFruitList("http://<your domain or IP>/fruits.json");
      try {
         synchronized (this){
            runOnUiThread(new Runnable() {
               @Override
               public void run() {
                  // Create adapter and set it to list view
                  final ArrayAdapter adapter = new
                     ArrayAdapter(MainActivity.this, R.layout.item, fruitList);
                  ListView listView = (ListView)findViewById(R.id.listView);
                  listView.setAdapter(adapter);
               }
            });
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
         MyIdlingResource.decrement(); // Set app as idle.
      }
   }
}

Hier wird die Frucht-URL als http: // <Ihre Domain oder IP / obst.json betrachtet und als JSON formatiert . Der Inhalt ist wie folgt:

[ 
   {
      "name":"Apple"
   },
   {
      "name":"Banana"
   },
   {
      "name":"Cherry"
   },
   {
      "name":"Dates"
   },
   {
      "name":"Elderberry"
   },
   {
      "name":"Fig"
   },
   {
      "name":"Grapes"
   },
   {
      "name":"Grapefruit"
   },
   {
      "name":"Guava"
   },
   {
      "name":"Jack fruit"
   },
   {
      "name":"Lemon"
   },
   {
      "name":"Mango"
   },
   {
      "name":"Orange"
   },
   {
      "name":"Papaya"
   },
   {
      "name":"Pears"
   },
   {
      "name":"Peaches"
   },
   {
      "name":"Pineapple"
   },
   {
      "name":"Plums"
   },
   {
      "name":"Raspberry"
   },
   {
      "name":"Strawberry"
   },
   {
      "name":"Watermelon"
   }
]

Note - Platzieren Sie die Datei auf Ihrem lokalen Webserver und verwenden Sie sie.

  • Suchen Sie nun die Ansicht, erstellen Sie einen neuen Thread, indem Sie FruitTask übergeben , erhöhen Sie die Leerlaufressource und starten Sie schließlich die Aufgabe.

// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
  • Der vollständige Code von MainActivity lautet wie folgt:

package com.tutorialspoint.espressosamples.myidlingfruitapp;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.test.espresso.idling.CountingIdlingResource;

import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
   @Nullable
   private CountingIdlingResource mIdlingResource = null;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      
      // Get data
      class FruitTask implements Runnable {
         ListView listView;
         CountingIdlingResource idlingResource;
         FruitTask(CountingIdlingResource idlingRes, ListView listView) {
            this.listView = listView;
            this.idlingResource = idlingRes;
         }
         public void run() {
            //code to do the HTTP request
            final ArrayList<String> fruitList = getFruitList(
               "http://<yourdomain or IP>/fruits.json");
            try {
               synchronized (this){
                  runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
                        // Create adapter and set it to list view
                        final ArrayAdapter adapter = new ArrayAdapter(
                           MainActivity.this, R.layout.item, fruitList);
                        ListView listView = (ListView) findViewById(R.id.listView);
                        listView.setAdapter(adapter);
                     }
                  });
               }
            } catch (Exception e) {
               e.printStackTrace();
            }
            if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
               MyIdlingResource.decrement(); // Set app as idle.
            }
         }
      }
      // Find list view
      ListView listView = (ListView) findViewById(R.id.listView);
      Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
      MyIdlingResource.increment();
      fruitTask.start();
   }
   private ArrayList<String> getFruitList(String data) {
      ArrayList<String> fruits = new ArrayList<String>();
      try {
         // Get url from async task and set it into a local variable
         URL url = new URL(data);
         Log.e("URL", url.toString());
         
         // Create new HTTP connection
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         
         // Set HTTP connection method as "Get"
         conn.setRequestMethod("GET");
         
         // Do a http request and get the response code
         int responseCode = conn.getResponseCode();
         
         // check the response code and if success, get response content
         if (responseCode == HttpURLConnection.HTTP_OK) {
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuffer response = new StringBuffer();
            while ((line = in.readLine()) != null) {
               response.append(line);
            }
            in.close();
            JSONArray jsonArray = new JSONArray(response.toString());
            Log.e("HTTPResponse", response.toString());
            
            for(int i = 0; i < jsonArray.length(); i++) {
               JSONObject jsonObject = jsonArray.getJSONObject(i);
               String name = String.valueOf(jsonObject.getString("name"));
               fruits.add(name);
            }
         } else {
            throw new IOException("Unable to fetch data from url");
         }
         conn.disconnect();
      } catch (IOException | JSONException e) {
         e.printStackTrace();
      }
      return fruits;
   }
}
  • Fügen Sie nun die folgende Konfiguration in die Anwendungsmanifestdatei AndroidManifest.xml ein

<uses-permission android:name = "android.permission.INTERNET" />
  • Kompilieren Sie nun den obigen Code und führen Sie die Anwendung aus. Der Screenshot der My Idling Fruit App sieht wie folgt aus:

  • Öffnen Sie nun die Datei ExampleInstrumentedTest.java und fügen Sie ActivityTestRule wie unten angegeben hinzu.

@Rule
public ActivityTestRule<MainActivity> mActivityRule = 
   new ActivityTestRule<MainActivity>(MainActivity.class);
Also, make sure the test configuration is done in app/build.gradle
dependencies {
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'androidx.test:runner:1.1.1'
   androidTestImplementation 'androidx.test:rules:1.1.1'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
   implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
   androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
  • Fügen Sie einen neuen Testfall hinzu, um die Listenansicht wie folgt zu testen:

@Before
public void registerIdlingResource() {
   IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
}
@Test
public void contentTest() {
   // click a child item
   onData(allOf())
   .inAdapterView(withId(R.id.listView))
   .atPosition(10)
   .perform(click());
}
@After
public void unregisterIdlingResource() {
   IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
}
  • Führen Sie den Testfall schließlich über das Kontextmenü von Android Studio aus und überprüfen Sie, ob alle Testfälle erfolgreich sind.

Android Intent wird verwendet, um neue Aktivitäten zu öffnen, entweder intern (Öffnen eines Produktdetailbildschirms über den Produktlistenbildschirm) oder extern (wie Öffnen eines Dialers, um einen Anruf zu tätigen). Interne Intent-Aktivitäten werden vom Espresso-Test-Framework transparent behandelt und erfordern keine spezifische Arbeit seitens des Benutzers. Das Aufrufen externer Aktivitäten ist jedoch eine echte Herausforderung, da dies außerhalb unseres Anwendungsbereichs liegt, der zu testenden Anwendung. Sobald der Benutzer eine externe Anwendung aufruft und die zu testende Anwendung verlässt, ist die Wahrscheinlichkeit geringer, dass der Benutzer mit einer vordefinierten Abfolge von Aktionen zur Anwendung zurückkehrt. Daher müssen wir die Benutzeraktion übernehmen, bevor wir die Anwendung testen. Espresso bietet zwei Optionen, um mit dieser Situation umzugehen. Sie sind wie folgt,

beabsichtigt

Auf diese Weise kann der Benutzer sicherstellen, dass die richtige Absicht in der zu testenden Anwendung geöffnet wird.

beabsichtigt

Auf diese Weise kann der Benutzer eine externe Aktivität verspotten, z. B. ein Foto von der Kamera aufnehmen, eine Nummer aus der Kontaktliste wählen usw. und mit vordefinierten Werten zur Anwendung zurückkehren (z. B. vordefiniertes Bild von der Kamera anstelle des tatsächlichen Bilds). .

Konfiguration

Espresso unterstützt die Intent-Option über eine Plugin-Bibliothek und die Bibliothek muss in der Gradle-Datei der Anwendung konfiguriert werden. Die Konfigurationsoption lautet wie folgt:

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}

beabsichtigt()

Das Espresso Intent Plugin bietet spezielle Matcher, um zu überprüfen, ob die aufgerufene Absicht die erwartete Absicht ist. Die bereitgestellten Matcher und der Zweck der Matcher sind wie folgt:

hasAction

Dies akzeptiert die Absichtsaktion und gibt einen Matcher zurück, der der angegebenen Absicht entspricht.

hasData

Dadurch werden die Daten akzeptiert und ein Matcher zurückgegeben, der die beim Abrufen bereitgestellten Daten mit der Absicht übereinstimmt.

toPackage

Dies akzeptiert den Intent-Paketnamen und gibt einen Matcher zurück, der mit dem Paketnamen der aufgerufenen Intent übereinstimmt.

Lassen Sie uns nun eine neue Anwendung erstellen und die Anwendung mit beabsichtigten () auf externe Aktivitäten testen , um das Konzept zu verstehen.

  • Starten Sie Android Studio.

  • Erstellen Sie ein neues Projekt wie zuvor beschrieben und nennen Sie es IntentSampleApp.

  • Migrieren Sie die Anwendung auf AndroidX Framework Umgestalten → Migrate zu AndroidX Optionsmenü.

  • Erstellen Sie ein Textfeld, eine Schaltfläche zum Öffnen der Kontaktliste und eine weitere zum Wählen eines Anrufs, indem Sie die Datei activity_main.xml wie unten gezeigt ändern.

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <EditText
      android:id = "@+id/edit_text_phone_number"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:text = ""
      android:autofillHints = "@string/phone_number"/>
   <Button
      android:id = "@+id/call_contact_button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/edit_text_phone_number"
      android:text = "@string/call_contact"/>
   <Button
      android:id = "@+id/button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/call_contact_button"
      android:text = "@string/call"/>
</RelativeLayout>
  • Fügen Sie außerdem die folgenden Artikel in strings.xml Ressourcendatei,

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • Fügen Sie nun den folgenden Code in die Hauptaktivität ( MainActivity.java ) unter der onCreate- Methode ein.

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // ... code
      // Find call from contact button
      Button contactButton = (Button) findViewById(R.id.call_contact_button);
      contactButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            // Uri uri = Uri.parse("content://contacts");
            Intent contactIntent = new Intent(Intent.ACTION_PICK,
               ContactsContract.Contacts.CONTENT_URI);
            contactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
            startActivityForResult(contactIntent, REQUEST_CODE);
         }
      });
      // Find edit view
      final EditText phoneNumberEditView = (EditText)
         findViewById(R.id.edit_text_phone_number);
      // Find call button
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            if(phoneNumberEditView.getText() != null) {
               Uri number = Uri.parse("tel:" + phoneNumberEditView.getText());
               Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
               startActivity(callIntent);
            }
         }
      });
   }
   // ... code
}

Hier haben wir die Taste mit der ID call_contact_button zum Öffnen der Kontaktliste und die Taste mit der ID, die Taste zum Wählen des Anrufs programmiert .

  • Fügen Sie eine statische Variable REQUEST_CODE in der MainActivity- Klasse hinzu, wie unten gezeigt.

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • Fügen Sie nun die onActivityResult- Methode in der MainActivity- Klasse wie folgt hinzu :

public class MainActivity extends AppCompatActivity {
   // ...
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == REQUEST_CODE) {
         if (resultCode == RESULT_OK) {
            // Bundle extras = data.getExtras();
            // String phoneNumber = extras.get("data").toString();
            Uri uri = data.getData();
            Log.e("ACT_RES", uri.toString());
            String[] projection = {
               ContactsContract.CommonDataKinds.Phone.NUMBER, 
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
            Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
            cursor.moveToFirst();
            
            int numberColumnIndex =
               cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            String number = cursor.getString(numberColumnIndex);
            
            int nameColumnIndex = cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            String name = cursor.getString(nameColumnIndex);
            Log.d("MAIN_ACTIVITY", "Selected number : " + number +" , name : "+name);
            
            // Find edit view
            final EditText phoneNumberEditView = (EditText)
               findViewById(R.id.edit_text_phone_number);
            phoneNumberEditView.setText(number);
         }
      }
   };
   // ...
}

Hier wird onActivityResult aufgerufen, wenn ein Benutzer zur Anwendung zurückkehrt, nachdem er die Kontaktliste mit der Schaltfläche call_contact_button geöffnet und einen Kontakt ausgewählt hat. Sobald die onActivityResult- Methode aufgerufen wird, erhält sie den vom Benutzer ausgewählten Kontakt, sucht die Kontaktnummer und legt sie in das Textfeld ein.

  • Führen Sie die Anwendung aus und stellen Sie sicher, dass alles in Ordnung ist. Das endgültige Erscheinungsbild der Intent-Beispielanwendung ist wie folgt:

  • Konfigurieren Sie nun die Espresso-Absicht in der Gradle-Datei der Anwendung wie unten gezeigt.

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Klicken Sie auf die Menüoption Jetzt synchronisieren, die vom Android Studio bereitgestellt wird. Dadurch wird die Absichtstestbibliothek heruntergeladen und ordnungsgemäß konfiguriert.

  • Öffnen Sie die Datei ExampleInstrumentedTest.java und fügen Sie die IntentsTestRule anstelle der normalerweise verwendeten AndroidTestRule hinzu . IntentTestRule ist eine spezielle Regel für das Testen von Absichten.

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • Fügen Sie zwei lokale Variablen hinzu, um die Testtelefonnummer und den Namen des Dialer-Pakets wie folgt festzulegen:

public class ExampleInstrumentedTest {
   // ... code
   private static final String PHONE_NUMBER = "1 234-567-890";
   private static final String DIALER_PACKAGE_NAME = "com.google.android.dialer";
   // ... code
}
  • Beheben Sie die Importprobleme mithilfe der von Android Studio bereitgestellten Option Alt + Eingabetaste oder fügen Sie die folgenden Importanweisungen hinzu:

import android.content.Context;
import android.content.Intent;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

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.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
  • Fügen Sie den folgenden Testfall hinzu, um zu testen, ob der Dialer ordnungsgemäß aufgerufen wird.

public class ExampleInstrumentedTest {
   // ... code
   @Test
   public void validateIntentTest() {
      onView(withId(R.id.edit_text_phone_number))
         .perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
      onView(withId(R.id.button)) .perform(click());
      intended(allOf(
         hasAction(Intent.ACTION_DIAL),
         hasData("tel:" + PHONE_NUMBER),
         toPackage(DIALER_PACKAGE_NAME)));
   }
   // ... code
}

Hier werden die Matcher hasAction , hasData und toPackage zusammen mit allOf Matcher verwendet, um nur dann erfolgreich zu sein, wenn alle Matcher übergeben wurden.

  • Führen Sie jetzt den ExampleInstrumentedTest über das Inhaltsmenü in Android Studio aus.

beabsichtigen ()

Espresso bietet eine spezielle Methode - intending () , um eine externe Absichtsaktion zu verspotten. intending () akzeptiert den Paketnamen der zu verspottenden Absicht und stellt eine Methode replyWith bereit, mit der festgelegt wird, wie auf die verspottete Absicht reagiert werden soll, wie unten angegeben.

intending(toPackage("com.android.contacts")).respondWith(result);

Hier akzeptiert replyWith () das Absichtsergebnis vom Typ Instrumentation.ActivityResult . Wir können eine neue Stub-Absicht erstellen und das Ergebnis manuell wie unten angegeben festlegen.

// Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
   new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);

Der vollständige Code zum Testen, ob eine Kontaktanwendung ordnungsgemäß geöffnet wurde, lautet wie folgt:

@Test
public void stubIntentTest() {
   // Stub intent
   Intent intent = new Intent();
   intent.setData(Uri.parse("content://com.android.contacts/data/1"));
   Instrumentation.ActivityResult result =
      new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
   intending(toPackage("com.android.contacts")).respondWith(result);
   
   // find the button and perform click action
   onView(withId(R.id.call_contact_button)).perform(click());
   
   // get context
   Context targetContext2 = InstrumentationRegistry.getInstrumentation().getTargetContext();
   
   // get phone number
   String[] projection = { ContactsContract.CommonDataKinds.Phone.NUMBER,
      ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
   Cursor cursor =
      targetContext2.getContentResolver().query(Uri.parse("content://com.android.cont
      acts/data/1"), projection, null, null, null);
   
   cursor.moveToFirst();
   int numberColumnIndex =
      cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
   String number = cursor.getString(numberColumnIndex);
   
   // now, check the data
   onView(withId(R.id.edit_text_phone_number))
   .check(matches(withText(number)));
}

Hier haben wir eine neue Absicht erstellt und den Rückgabewert (beim Aufrufen der Absicht) als ersten Eintrag in der Kontaktliste festgelegt, Inhalt: //com.android.contacts/data/1 . Dann haben wir die beabsichtigte Methode festgelegt, um die neu erstellte Absicht anstelle der Kontaktliste zu verspotten. Es legt unsere neu erstellte Absicht fest und ruft sie auf, wenn das Paket com.android.contacts aufgerufen und der erste Standardeintrag der Liste zurückgegeben wird. Dann haben wir die Aktion click () ausgelöst, um die Scheinabsicht zu starten, und schließlich überprüft, ob die Telefonnummer beim Aufrufen der Scheinabsicht und die Nummer des ersten Eintrags in der Kontaktliste identisch sind.

Wenn ein Importproblem fehlt, beheben Sie diese Importprobleme mithilfe der von Android Studio bereitgestellten Option Alt + Eingabetaste oder fügen Sie die folgenden Importanweisungen hinzu.

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

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.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
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.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;

Fügen Sie der Testklasse die folgende Regel hinzu, um die Berechtigung zum Lesen der Kontaktliste zu erteilen.

@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);

Fügen Sie die folgende Option in die Anwendungsmanifestdatei hinzu: AndroidManifest.xml -

<uses-permission android:name = "android.permission.READ_CONTACTS" />

Stellen Sie nun sicher, dass die Kontaktliste mindestens einen Eintrag enthält, und führen Sie den Test über das Kontextmenü von Android Studio aus.

Android unterstützt das Testen von Benutzeroberflächen, an denen mehr als eine Anwendung beteiligt ist. Nehmen wir an, unsere Anwendung hat die Option, von unserer Anwendung zur Messaging-Anwendung zu wechseln, um eine Nachricht zu senden, und kehrt dann zu unserer Anwendung zurück. In diesem Szenario hilft uns das UI-Automator-Testframework beim Testen der Anwendung. Der UI-Automator kann als guter Begleiter für das Espresso-Test-Framework angesehen werden. Wir können die Option intending () im Espresso-Test-Framework nutzen, bevor wir uns für UI Automator entscheiden .

Setup-Anweisung

Android bietet UI Automator als separates Plugin. Es muss in der app / build.gradle wie unten angegeben konfiguriert werden.

dependencies {
   ...
   androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

Workflow zum Schreiben von Testfällen

Lassen Sie uns verstehen, wie ein UI Automator- basierter Testfall geschrieben wird.

  • Rufen Sie das UiDevice- Objekt ab, indem Sie die Methode getInstance () aufrufen und das Instrumentation- Objekt übergeben.

myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
myDevice.pressHome();
  • Holen UIObject - Objekt der Verwendung von Findobject () Methode. Bevor wir diese Methode verwenden, können wir die Anwendung uiautomatorviewer öffnen , um die UI-Komponenten der Zielanwendung zu überprüfen , da wir durch das Verständnis der Zielanwendung bessere Testfälle schreiben können.

UiObject button = myDevice.findObject(new UiSelector()
   .text("Run")
   .className("android.widget.Button"));
  • Simulieren Sie die Benutzerinteraktion, indem Sie die Methode von UiObject aufrufen . Setzen Sie beispielsweise setText () , um ein Textfeld zu bearbeiten, und klicken Sie auf () , um ein Klickereignis einer Schaltfläche auszulösen .

if(button.exists() && button.isEnabled()) {
   button.click();
}
  • Schließlich prüfen wir, ob die Benutzeroberfläche den erwarteten Status widerspiegelt.

Das Schreiben eines Testfalls ist eine mühsame Aufgabe. Obwohl Espresso eine sehr einfache und flexible API bietet, kann das Schreiben von Testfällen eine faule und zeitaufwändige Aufgabe sein. Um dies zu überwinden, bietet Android Studio eine Funktion zum Aufzeichnen und Generieren von Espresso-Testfällen. Record Espresso Test ist im Menü Run verfügbar .

Lassen Sie uns einen einfachen Testfall in unserer HelloWorldApp aufzeichnen, indem Sie die unten beschriebenen Schritte ausführen:

  • Öffnen Sie das Android Studio, gefolgt von der HelloWorldApp- Anwendung.

  • Klicken Sie auf AusführenEspressotest aufzeichnen und wählen Sie MainActivity .

  • Der Recorder- Screenshot sieht wie folgt aus:

  • Klicken Sie auf Assertion hinzufügen . Der Anwendungsbildschirm wird wie unten gezeigt geöffnet.

  • Klicken Sie auf Hallo Welt! . Der Bildschirm des Rekorders zur Auswahl der Textansicht lautet wie folgt:

  • Klicken Sie erneut auf Zusicherung speichern. Dadurch wird die Zusicherung gespeichert und wie folgt angezeigt:

  • Klicken Sie auf OK . Es öffnet sich ein neues Fenster und Sie werden nach dem Namen des Testfalls gefragt. Der Standardname ist MainActivityTest

  • Ändern Sie gegebenenfalls den Namen des Testfalls.

  • Klicken Sie erneut auf OK . Dadurch wird eine Datei, MainActivityTest, mit unserem aufgezeichneten Testfall generiert . Die vollständige Codierung lautet wie folgt:

package com.tutorialspoint.espressosamples.helloworldapp;

import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.espresso.ViewInteraction;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;

@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
   @Rule
   public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
   @Test
   public void mainActivityTest() {
      ViewInteraction textView = onView(
         allOf(withId(R.id.textView_hello), withText("Hello World!"),
         childAtPosition(childAtPosition(withId(android.R.id.content),
         0),0),isDisplayed()));
      textView.check(matches(withText("Hello World!")));
   }
   private static Matcher<View> childAtPosition(
      final Matcher<View> parentMatcher, final int position) {
      return new TypeSafeMatcher<View>() {
         @Override
         public void describeTo(Description description) {
            description.appendText("Child at position " + position + " in parent ");
            parentMatcher.describeTo(description);
         }
         @Override
         public boolean matchesSafely(View view) {
            ViewParent parent = view.getParent();
            return parent instanceof ViewGroup &&
               parentMatcher.matches(parent)&& view.equals(((ViewGroup)
               parent).getChildAt(position));
         }
      };
   }
}
  • Führen Sie den Test abschließend über das Kontextmenü aus und prüfen Sie, ob der Testfall ausgeführt wird.

Positive Benutzererfahrung spielt eine sehr wichtige Rolle für den Erfolg einer Anwendung. Die Benutzererfahrung umfasst nicht nur schöne Benutzeroberflächen, sondern auch, wie schnell diese schönen Benutzeroberflächen gerendert werden und wie hoch die Rate pro Sekunde ist. Die Benutzeroberfläche muss konsistent mit 60 Bildern pro Sekunde ausgeführt werden, um eine gute Benutzererfahrung zu erzielen.

Lassen Sie uns einige der im Android verfügbaren Optionen zur Analyse der UI-Leistung in diesem Kapitel kennenlernen.

dumpsys

dumpsys ist ein eingebautes Tool, das auf dem Android-Gerät verfügbar ist. Es gibt aktuelle Informationen zu den Systemdiensten aus. dumpsys bietet die Möglichkeit, Informationen zu einer bestimmten Kategorie zu sichern . Wenn Sie gfxinfo übergeben , erhalten Sie Animationsinformationen zum mitgelieferten Paket. Der Befehl lautet wie folgt:

> adb shell dumpsys gfxinfo <PACKAGE_NAME>

Framestats

framestats ist eine Option des Befehls dumpsys. Sobald dumpsys mit Framestats aufgerufen wird , werden detaillierte Frame-Timing-Informationen der letzten Frames ausgegeben . Der Befehl lautet wie folgt:

> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats

Die Informationen werden als CSV (durch Kommas getrennte Werte) ausgegeben. Die Ausgabe im CSV-Format hilft dabei, die Daten einfach in Excel zu übertragen und anschließend nützliche Informationen über Excel-Formeln und -Diagramme zu extrahieren.

systrace

systrace ist auch ein eingebautes Tool, das auf dem Android-Gerät verfügbar ist. Es erfasst und zeigt die Ausführungszeiten der Anwendungsprozesse an. systrace kann mit dem folgenden Befehl im Terminal des Android Studios ausgeführt werden.

python %ANDROID_HOME%/platform-tools/systrace/systrace.py --time=10 -o
my_trace_output.html gfx view res

Die Eingabehilfenfunktion ist eine der Hauptfunktionen für jede Anwendung. Die von einem Anbieter entwickelte Anwendung sollte die vom Android SDK festgelegten Mindestrichtlinien für die Barrierefreiheit unterstützen, um eine erfolgreiche und nützliche Anwendung zu sein. Das Befolgen des Barrierefreiheitsstandards ist sehr wichtig und keine leichte Aufgabe. Das Android SDK bietet großartige Unterstützung, indem es richtig gestaltete Ansichten bereitstellt, um zugängliche Benutzeroberflächen zu erstellen.

In ähnlicher Weise ist das Espresso-Testframework sowohl für Entwickler als auch für Endbenutzer von großem Vorteil, da es die Funktionen zum Testen der Barrierefreiheit in der Kerntest-Engine transparent unterstützt.

In Espresso kann ein Entwickler Barrierefreiheitstests über die AccessibilityChecks- Klasse aktivieren und konfigurieren . Der Beispielcode lautet wie folgt:

AccessibilityChecks.enable();

Standardmäßig werden die Eingabehilfenprüfungen ausgeführt, wenn Sie eine Ansichtsaktion ausführen. Die Prüfung umfasst die Ansicht, für die die Aktion ausgeführt wird, sowie alle untergeordneten Ansichten. Sie können die gesamte Ansichtshierarchie eines Bildschirms mit dem folgenden Code überprüfen:

AccessibilityChecks.enable().setRunChecksFromRootView(true);

Fazit

Espresso ist ein großartiges Tool für Android-Entwickler, mit dem sie ihre Anwendung auf sehr einfache Weise und ohne zusätzlichen Aufwand, der normalerweise für ein Testframework erforderlich ist, vollständig testen können. Es gibt sogar einen Rekorder, mit dem Testfälle erstellt werden können, ohne den Code manuell zu schreiben. Darüber hinaus werden alle Arten von Benutzeroberflächentests unterstützt. Durch die Verwendung des Espresso-Test-Frameworks kann ein Android-Entwickler in kurzer Zeit sicher eine gut aussehende Anwendung sowie eine erfolgreiche Anwendung ohne Probleme entwickeln.