User Interfaces in Kotlin testen | Wie man sie implementiert
Wenn wir uns die Reihe der Code Labs „Advanced Android in Kotlin“ auf Google ansehen, finden wir eines über Testing und Dependency Injection. Es ist nicht nur eine Testpraxis, sondern auch ein Architekturleitfaden für Android.
Wenn Sie zum dritten Abschnitt kommen, werden Sie anfangen, UI-Tests mit Espresso zu schreiben. Es ist großartig, sie auszuführen und zu sehen, wie die App von selbst läuft und Dinge tut. Obwohl als fortgeschrittenes Code-Lab archiviert, kann der Code für den Test mit dem Page Object Pattern refaktorisiert werden.
Seitenobjekt-Muster
Der Zweck eines Seitenobjekts ist es, den „fremden“ Code zu verstecken, der benötigt wird, um die Schritte zu replizieren, die notwendig sind, um in unserer Anwendung zu navigieren, Aktionen als Benutzer auszuführen und eine saubere Schnittstelle bereitzustellen, mit der nützliche und sinnvolle Tests erstellt werden können.
Um dies in Aktion zu sehen, werden wir den Code aus dem End Solution Lab Code Repo verwenden, der hier zu finden ist.
Wir werden mit dem Zweig „end_codelab_3“ arbeiten.
Seitenobjekt in Kotlin
Wir beginnen mit dem Hinzufügen der Kotlin base Page-Klasse, die die Magie in Gang setzen wird. Fügen Sie diese Klasse zum Quellenset „androidTest“ hinzu:
open class Page {
companion object {
inline fun <reified T : Page> on(): T {
return Page().on()
}
}
inline fun <reified T : Page> on(): T {
val page = T::class.constructors.first().call()
page.verify()
//Thread.sleep(500) //Zur Überbrückung bei asynchronen Aufrufen
return page
}
open fun verify(): Page {
// Jede Unterseite sollte hier ihre Standardgarantien haben
return this
}
fun back(): Page {
Espresso.pressBack()
return this
}
}
Wenn Sie einen genaueren Blick auf Page Object Pattern in Kotlin for UI Test Automation On Android von Kate Savelova werfen wollen.
Letztendlich dient diese Klasse dazu, eine flüssige API zu definieren, die uns dabei hilft, den Code zu organisieren, der für die Navigation, Aktionen und Prüfungen auf den Seiten, Ansichten usw. unserer App benötigt wird. Achten Sie besonders auf die Verify-Funktion. Diese Funktion wird verwendet, um zu prüfen, ob die gewünschte Seite geladen wurde.
Beginnen wir damit, unseren ersten Test mit dieser Seite hinzuzufügen. Gehen Sie zur Datei AppNavigationTest.kt und fügen Sie einen neuen Test hinzu, der der App eine neue Aufgabe hinzufügt.
@Test
fun createNewTask() {
// Starten des Bildschirms Aufgaben
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
dataBindingIdlingResource.monitorActivity(activityScenario)
// Wenn Sie ActivityScenario.launch verwenden, rufen Sie immer close() auf
activityScenario.close()
}
Dies ist der Startcode für den Test, der gerade die TasksActivity gestartet hat. Für ein tieferes Verständnis schauen Sie sich das Codelabor hier an.
Der vollständige Code für den Test lautet:
@Test
fun createNewTask() {
// Starten des Bildschirms Tasks
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
dataBindingIdlingResource.monitorActivity(activityScenario)
val testTask= Aufgabe(„Titel“, „Beschreibung“)
Page.on<TasksPage>()
.tapOnAddButton()
.on<AddTaskPage>()
.addTask(testTask)
.on<AufgabenSeite>()
.checkAddedTask(testTask)
// Wenn Sie ActivityScenario.launch verwenden, rufen Sie immer close() auf
activityScenario.close()
}
Sehen Sie sich die Schönheit dieses Tests an:
-
- Erstellen Sie eine Aufgabe
-
- Klicken Sie auf der Aufgabenseite auf die Schaltfläche Hinzufügen
-
- Auf der Seite AddTaskPage fügen Sie die Aufgabe hinzu, die wir in Schritt 1 erstellt haben
- Überprüfen Sie nun auf der Aufgabenseite, ob die Aufgabe hinzugefügt wurde
Dies ist sehr sinnvoll und einfach. Es lässt sich noch nicht kompilieren, aber keine Sorge. Wir werden das alles noch in Ordnung bringen:
Fügen Sie die gradle-Abhängigkeit zur build.gradle-Anwendung hinzu:
- Implementierung „org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion“
TasksPage und AddTaskPage Klassen
Fügen Sie die Klassen TasksPage und AddTaskPage in das gleiche PageObject-Paket ein:
class TasksPage : Page() {
override fun verify(): TasksPage {
onView(withId(R.id.tasks_container_layout))
.check(matches(isDisplayed()))
return this
}
fun tapOnAddButton(): TasksPage {
onView(withId(R.id.add_task_fab)).perform(ViewActions.click())
return this
}
fun tapOnEditTask(): TasksPage {
onView(withId(R.id.edit_task_fab)).perform(ViewActions.click())
return this
}
fun checkAddedTask(testTask: Aufgabe): TasksPage {
onView(withText(testTask.title))
return this
}
}
class AddTaskPage: Seite() {
override fun verify(): AddTaskPage {
Espresso.onView(withId(R.id.add_task_title_edit_text))
.check(ViewAssertions.matches(isDisplayed())))
return this
}
fun addTask(task: Task):AddTaskPage{
onView(withId(R.id.add_task_title_edit_text))
.perform(clearText(), typeText(task.title))
onView(withId(R.id.add_task_description_edit_text))
.perform(clearText(), typeText(task.description))
onView(withId(R.id.save_task_fab)).perform(click())
return this
}
}
Enthält den gesamten Espresso-Code, der für die Durchführung aller Interaktionen erforderlich ist. Wenn wir dieses Muster nicht verwenden, haben wir am Ende einen sehr langen Test mit all diesen Espresso-Methoden in einem einzigen Test, was das Lesen und die Wartung sehr erschwert.
Am Ende läuft diese Anwendung auf dem Handy:
Der äquivalente Test ohne die Verwendung des Page Object-Musters lautet:
@Test
fun createNewTaskWithoutPageObject(){
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
dataBindingIdlingResource.monitorActivity(activityScenario)
val task= Aufgabe(„Titel“, „Beschreibung“)
//Überprüfen der geöffneten Aufgabenseite
onView(withId(R.id.tasks_container_layout))
.check(matches(isDisplayed())))
//Tippen Sie auf die Schaltfläche Hinzufügen
onView(withId(R.id.add_task_fab)).perform(ViewActions.click())
//Überprüfen, ob die Aufgabenseite geöffnet ist
onView(withId(R.id.add_task_title_edit_text))
.check(ViewAssertions.matches(isDisplayed())))
//Aufgabe hinzufügen
onView(withId(R.id.add_task_title_edit_text))
.perform(ViewActions.clearText(), ViewActions.typeText(task.title))
onView(withId(R.id.add_task_description_edit_text))
.perform(ViewActions.clearText(), ViewActions.typeText(task.description))
onView(withId(R.id.save_task_fab)).perform(click())
//Überprüfen, ob die Aufgabenseite geöffnet ist
onView(withId(R.id.tasks_container_layout))
.check(matches(isDisplayed())))
//überprüfe hinzugefügte Aufgabe
onView(withText(task.title))
// Bei Verwendung von ActivityScenario.launch, immer close() aufrufen
activityScenario.close()
}
Welchen Testcode bevorzugen Sie?
Die Vorteile der Verwendung dieses Musters zur Erstellung von UI-Tests sind:
- Sinnvolle UI-Tests:
- Wie Sie sehen können, ist der UI-Testcode sehr anschaulich, er ist wie ein Roman geschrieben.
- Wartung:
- Jedes Mal, wenn sich die Benutzeroberfläche ändert, müssen wir nur die Seiten ändern, die von der Änderung betroffen sind. Aber mit dieser Architektur ist es einfach, sie zu finden und herauszufinden, warum.
- Das Hinzufügen neuer Tests wird einfach, um doppelten Code zu vermeiden.
Zusammenfassungen
Page Object ist ein großartiges Muster, das für UI-Tests auf anderen Plattformen wie Java, JavaScript, C# usw. weit verbreitet ist. Und wie Sie sehen können, ist es sehr einfach, wenn Sie es einmal verstanden haben. Wir hoffen, dass Sie diesen Beitrag nützlich fanden!
Sie können eine vollständige Version dieses Codes in meiner Google Code Fork sehen.
Und wenn Sie ein Ktolin-Experte werden wollen, sollten Sie diese Sitzung mit meinem Kollegen Miguel Ángel Barrera nicht verpassen, in der wir über alle Geheimnisse von Ktolin sprechen werden.