Questo codelab fa parte del corso Advanced Android in Kotlin. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab, ma non è obbligatorio. Tutti i codelab del corso sono elencati nella pagina di destinazione avanzata per i codelab di Android in Kotlin.
Introduzione
Quando hai implementato la prima funzionalità della tua prima app, è probabile che tu abbia eseguito il codice per verificare che abbia funzionato come previsto. Hai eseguito un test, anche se è stato eseguito un test manuale. Mentre continuavi ad aggiungere e aggiornare le funzionalità, probabilmente hai continuato a eseguire il codice e a verificare che funzioni. ma farlo manualmente ogni volta è stancante, soggetto a errori e non scala.
I computer sono perfetti per la scalabilità e l'automazione. Pertanto, gli sviluppatori di società di grandi e piccole dimensioni scrivono test automatici, ovvero test eseguiti da software e che non richiedono l'utilizzo manuale dell'app per verificare il funzionamento del codice.
In questa serie di codelab imparerai a creare una raccolta di test (nota come suite di test) per un'app reale.
Questo primo codelab illustra i concetti di base dei test su Android, scriverai i tuoi primi test e scoprirai come testare LiveData
e ViewModel
.
Informazioni importanti
Dovresti acquisire familiarità con:
- Il linguaggio di programmazione Kotlin
- Le seguenti librerie principali di Android Jetpack:
ViewModel
eLiveData
- Architettura dell'applicazione, seguendo lo schema indicato nella Guida all'architettura delle app e i codelab di Android Fundamentals.
Obiettivi didattici
Scoprirai i seguenti argomenti:
- Come scrivere ed eseguire test delle unità su Android
- Come utilizzare lo sviluppo basato su test
- Come scegliere i test con strumentazione e i test locali
Scoprirai le seguenti librerie e concetti di codice:
- JUnit4
- Hamcrest
- Libreria di test di AndroidX
- Libreria di test per i componenti dell'architettura AndroidX
In questo lab proverai a:
- Configurazione, esecuzione e interpretazione dei test locali e degli strumenti in Android.
- Scrivere test delle unità in Android utilizzando JUnit4 e Hamcrest.
- Scrivere test
LiveData
eViewModel
semplici.
In questa serie di codelab, lavorerai con l'app Notes da fare. L'app ti consente di scrivere le attività da completare e di visualizzarle in un elenco. Puoi quindi contrassegnarli come completati o meno, filtrarli o eliminarli.
Questa app è scritta in Kotlin, ha diversi schermi, utilizza componenti Jetpack e segue l'architettura da una guida all'architettura delle app. Imparando a testare questa app, sarai in grado di testare app che utilizzano le stesse librerie e la stessa architettura.
Per iniziare, scarica il codice:
In alternativa, puoi clonare il repository GitHub per il codice:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
In questa attività eseguirai l'app ed esplorerai il codebase.
Passaggio 1: esegui l'app di esempio
Dopo aver scaricato l'app TO-DO, aprila in Android Studio ed eseguila. Dovrebbe essere compilata. Esplora l'app procedendo nel seguente modo:
- Crea una nuova attività con il pulsante più azione. Inserisci prima un titolo e altre informazioni sull'attività. Salvala con il FAB di controllo verde.
- Nell'elenco delle attività, fai clic sul titolo dell'attività che hai appena completato e controlla la schermata dei dettagli per visualizzare il resto della descrizione.
- Nell'elenco o nella schermata dei dettagli, seleziona la casella di controllo dell'attività per impostarne lo stato su Completata.
- Torna alla schermata Attività, apri il menu dei filtri e filtra le attività in base allo stato Attivo e Completato.
- Apri il riquadro di navigazione a scomparsa e fai clic su Statistiche.
- Quando torni alla schermata Panoramica, seleziona Cancella completate dal menu del riquadro di navigazione per eliminare tutte le attività con stato Completata.
Passaggio 2: esplora il codice dell'app di esempio
L'app TO-DO si basa sul popolare esempio di architettura e test Architecture Blueprints (utilizzando la versione architettura reattiva del campione). L'app segue l'architettura descritta in una guida all'architettura dell'app. Utilizzo di ViewModels con Fragments, un repository e Room. Se conosci uno degli esempi riportati di seguito, questa app ha un'architettura simile:
- Stanza con un codelab sulla visualizzazione
- Codelab di formazione su Android Kotlin Fundamentals
- Codelab sulla formazione Android avanzata
- Esempio di girasole per Android
- Sviluppo di app Android con il corso di formazione Kotlin Udacity
È più importante che tu comprenda l'architettura generale dell'app piuttosto che comprendere a fondo la logica di un qualsiasi livello.
Ecco un riepilogo dei pacchetti disponibili:
Pacco: | |
| La schermata Aggiungi o modifica un'attività: codice dell'interfaccia utente per aggiungere o modificare un'attività. |
| Livello dati: include il livello dati delle attività. Contiene il codice del database, della rete e del repository. |
| Schermata delle statistiche: codice del livello dell'interfaccia utente per la schermata delle statistiche. |
| Schermata Dettagli attività: codice del livello UI per una singola attività. |
| Schermata Attività: codice del livello UI per l'elenco di tutte le attività. |
| Corsi di utilità: corsi condivisi utilizzati in diverse parti dell'app, ad esempio per il layout di aggiornamento dello scorrimento utilizzato su più schermate. |
Livello dati (.data)
Questa app include un livello di rete simulato, nel pacchetto remote, e un livello database, nel pacchetto local. Per semplicità, in questo progetto il livello di networking viene simulato con un semplice HashMap
, con un ritardo, anziché effettuare richieste di rete reali.
Le coordinate o le mediazioni di DefaultTasksRepository
tra il livello di networking e il livello di database sono i dati che restituiscono i dati al livello dell'interfaccia utente.
Livello UI ( .aggiungereittask, .statistic, .taskdetail, .tasks)
Ciascuno dei pacchetti di livelli UI contiene un frammento e un modello di visualizzazione, insieme alle altre classi necessarie per l'interfaccia utente (ad esempio un adattatore per l'elenco delle attività). TaskActivity
è l'attività che contiene tutti i frammenti.
Navigazione
La navigazione per l'app è controllata dal componente Navigazione. È definito nel file nav_graph.xml
. La navigazione viene attivata nei modelli delle viste utilizzando la classe Event
; anche i modelli di vista determinano gli argomenti da trasmettere. I frammenti osservano i Event
e si spostano effettivamente tra le schermate.
In questa attività, eseguirai i primi test.
- In Android Studio, apri il riquadro Progetto e individua le tre cartelle seguenti:
com.example.android.architecture.blueprints.todoapp
com.example.android.architecture.blueprints.todoapp (androidTest)
com.example.android.architecture.blueprints.todoapp (test)
Queste cartelle sono note come set di origini. I set di origine sono cartelle contenenti codice sorgente della tua app. I set di origini di colore verde (androidTest e test) contengono i tuoi test. Per impostazione predefinita, quando crei un nuovo progetto Android vengono visualizzati i seguenti tre set di origini. Sono:
main
: contiene il codice della tua app. Questo codice è condiviso tra tutte le diverse versioni dell'app che puoi creare (note come varianti della build)androidTest
: contiene test chiamati test strumentali.test
: contiene test noti come test locali.
La differenza tra test locali e test con strumenti viene valutata nel modo in cui vengono eseguiti.
Test locali (test
set di origini)
Questi test vengono eseguiti localmente sulla JVM della tua macchina di sviluppo e non richiedono un emulatore o un dispositivo fisico. Per questo motivo, corrono velocemente, ma la loro fedeltà è inferiore, il che significa che agiscono meno come nel mondo reale.
In Android Studio, i test locali sono rappresentati da un'icona triangolare verde e rossa.
Test con strumentazione (androidTest
set di origini)
Questi test vengono eseguiti su dispositivi Android reali o emulati, quindi riflettono ciò che accadrà nel mondo reale, ma sono anche molto più lenti.
I test con strumenti di Android Studio sono rappresentati da un'icona Android con un triangolo verde e un'icona rossa.
Passaggio 1: esegui un test locale
- Apri la cartella
test
finché non trovi il file ExampleUnitTest.kt. - Fai clic con il tasto destro del mouse e seleziona Esegui ExampleUnitTest.
Dovresti vedere il seguente output nella finestra Run (Esegui) nella parte inferiore dello schermo:
- Osserva i segni di spunta verdi ed espandi i risultati del test per confermare che un test chiamato
addition_isCorrect
sia stato superato. È molto utile sapere che l'aggiunta funziona come previsto.
Passaggio 2: il test non viene superato
Di seguito è riportato il test che hai eseguito.
ExampleUnitTest.kt
// A test class is just a normal class
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
// Here you are checking that 4 is the same as 2+2
assertEquals(4, 2 + 2)
}
}
Nota che i test
- sono una classe di uno dei set di origini di test.
- contengono funzioni che iniziano con l'annotazione
@Test
(ogni funzione è un singolo test). - {0}Di solito contengono dichiarazioni asseritive.
Android utilizza la libreria di test JUnit per i test (in questo codelab JUnit4). Sia le asserzioni che l'annotazione @Test
provengono da JUnit.
L'elemento principale del test è una valutazione. È un'istruzione di verifica del codice che verifichi il comportamento del codice o dell'app come previsto. In questo caso, l'asserzione è assertEquals(4, 2 + 2)
che verifica che 4 sia uguale a 2 + 2.
Per scoprire come funziona un test in errore, aggiungi un'affermazione che puoi facilmente individuare non dovrebbe riuscire. Controllo 3 è uguale a 1+1.
- Aggiungi
assertEquals(3, 1 + 1)
al testaddition_isCorrect
.
ExampleUnitTest.kt
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
assertEquals(3, 1 + 1) // This should fail
}
}
- Esegui il test.
- Nei risultati, controlla la presenza di una X accanto al test.
- Nota:
- Una singola asserzione non riuscita non supera l'intero test.
- Ti viene indicato il valore previsto (3) rispetto al valore calcolato effettivamente (2).
- Il sistema ti reindirizzerà alla riga dell'asserzione non riuscita
(ExampleUnitTest.kt:16)
.
Passaggio 3: esegui un test con strumentazione
I test con strumentazione fanno parte del set di origini androidTest
.
- Apri il set di origini
androidTest
. - Esegui il test
ExampleInstrumentedTest
.
ExampleInstrumentedTest
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.android.architecture.blueprints.reactive",
appContext.packageName)
}
}
A differenza del test locale, questo test viene eseguito su un dispositivo (nell'esempio riportato di seguito, su un telefono Pixel 2 emulato):
Se hai un dispositivo collegato o un emulatore in esecuzione, dovresti vedere il test in esecuzione sull'emulatore.
In questa attività scriverai test per getActiveAndCompleteStats
, che calcolano la percentuale di statistiche sulle attività attive e complete relative alla tua app. Puoi visualizzare questi numeri nella schermata delle statistiche dell'app.
Passaggio 1: crea un corso di prova
- Nel set di origine di
main
, intodoapp.statistics
, apriStatisticsUtils.kt
. - Trova la funzione
getActiveAndCompletedStats
.
Statistiche Util.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)
La funzione getActiveAndCompletedStats
accetta un elenco di attività e restituisce un StatsResult
. StatsResult
è una classe di dati contenente due numeri, la percentuale di attività completate e la percentuale attiva.
Android Studio ti fornisce gli strumenti per generare stub di test che ti aiutano a implementare i test per questa funzione.
- Fai clic con il pulsante destro del mouse su
getActiveAndCompletedStats
e seleziona Genera > Test.
Viene visualizzata la finestra di dialogo Crea test:
- Cambia il Nome del corso: in
StatisticsUtilsTest
(anzichéStatisticsUtilsKtTest
; è preferibile non utilizzare KT nel nome del corso). - Mantieni le altre impostazioni predefinite. JUnit 4 è la libreria di test appropriata. Il pacchetto di destinazione è corretto (in base alla posizione della classe
StatisticsUtils
) e non è necessario selezionare alcuna casella di controllo (questa opzione ti permette di generare un codice aggiuntivo, ma il test verrà scritto da zero). - Premi OK
Si apre la finestra di dialogo Choose Destination Directory (Scegli directory di destinazione):
Verificherai un test locale perché la tua funzione esegue calcoli matematici e non includerà alcun codice specifico per Android. Pertanto, non è necessario eseguirla su un dispositivo reale o emulato.
- Seleziona la directory
test
(nonandroidTest
) perché scriverai i test locali. - Fai clic su OK.
- Osserva la classe
StatisticsUtilsTest
generata intest/statistics/
.
Passaggio 2: scrivi la prima funzione di test
Stai per scrivere un test che verifica:
- se non ci sono attività completate e un'attività attiva
- che la percentuale di test attivi sia del 100%,
- e la percentuale di attività completate è pari allo 0%.
- Apri
StatisticsUtilsTest
. - Crea una funzione denominata
getActiveAndCompletedStats_noCompleted_returnsHundredZero
.
StatisticheUtils.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- Aggiungi l'annotazione
@Test
sopra il nome della funzione per indicare che si tratta di un test. - Crea un elenco di attività.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- Chiama
getActiveAndCompletedStats
con queste attività.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- Utilizzando le asserzioni, verifica che
result
sia quello che ti aspettavi.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
Ecco il codice completo.
StatisticheUtils.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
}
}
- Esegui il test (fai clic con il pulsante destro del mouse su
StatisticsUtilsTest
e seleziona Esegui).
L'account dovrebbe superare:
Passaggio 3: aggiungi la dipendenza Hamcrest
Dato che i tuoi test agiscono come documentazione di ciò che fa il tuo codice, è utile quando sono leggibili da una persona. Confronta le due affermazioni seguenti:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
La seconda affermazione è molto più simile a una frase umana. La struttura utilizza un framework di asserzioni chiamato Hamcrest. Un altro valido strumento per scrivere asserzioni leggibili è la Libreria Truth. Utilizzerai Hamcrest in questo codelab per scrivere affermazioni.
- Apri
build.grade (Module: app)
e aggiungi la seguente dipendenza.
app/build.gradle
dependencies {
// Other dependencies
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}
In genere utilizzi implementation
per aggiungere una dipendenza, ma qui utilizzi testImplementation
. Quando sei pronto a condividere la tua app con il mondo intero, è preferibile non gonfiare le dimensioni dell'APK con qualsiasi codice o dipendenza di test nella tua app. Puoi stabilire se una libreria deve essere inclusa nel codice principale o di test utilizzando le configurazioni gradle. Le configurazioni più comuni sono:
implementation
: la dipendenza è disponibile in tutti i set di origini, inclusi quelli di test.testImplementation
: la dipendenza è disponibile solo nell'insieme di origini di test.androidTestImplementation
: la dipendenza è disponibile solo nel set di originiandroidTest
.
La configurazione che utilizzi definisce dove può essere utilizzata la dipendenza. Se scrivi:
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
Ciò significa che Hamcrest sarà disponibile solo nell'insieme di fonti di test. Garantisce inoltre che Hamcrest non sia incluso nella tua app finale.
Passaggio 4: utilizza Hamcrest per scrivere affermazioni
- Aggiorna il test
getActiveAndCompletedStats_noCompleted_returnsHundredZero()
per utilizzareassertThat
di Hamcrest anzichéassertEquals
.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
Tieni presente che puoi utilizzare l'importazione import org.hamcrest.Matchers.`is`
, se richiesto.
Il test finale avrà il seguente aspetto.
StatisticheUtils.kt
import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
// Create an active tasks (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
- Esegui il test aggiornato per verificare che funzioni ancora.
Questo codelab non ti mostrerà tutti i dettagli di Hamcrest, quindi se vuoi saperne di più, guarda il tutorial ufficiale.
Questa è un'attività facoltativa facoltativa.
In questa attività dovrai scrivere altri test utilizzando JUnit e Hamcrest. Scrivi i test utilizzando anche una strategia derivata dalla prassi del programma Test Driven Development (Sviluppo guidato da test). Lo sviluppo basato su test TDD è una scuola di programmazione che afferma che prima di scrivere il codice delle funzionalità devi prima scrivere i test. Dopodiché scrivi il codice della funzionalità con l'obiettivo di superare i test.
Passaggio 1. Scrivere i test
Scrivi i test quando hai un normale elenco di attività:
- Se è presente un'attività completata e nessuna attività attiva, la percentuale di
activeTasks
deve essere pari a0f
e la percentuale di attività completate deve essere100f
. - Se ci sono due attività completate e tre attività attive, la percentuale completata deve essere
40f
e la percentuale attiva dovrebbe essere60f
.
Passaggio 2. Scrivere un test per un bug
Il codice per il getActiveAndCompletedStats
come scritto presenta un bug. Nota come non gestisce correttamente cosa succede se l'elenco è vuoto o nullo. In entrambi i casi, entrambe le percentuali devono essere pari a zero.
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
Per correggere il codice e scrivere i test, devi utilizzare lo sviluppo basato su test. Sviluppo basato su test segue questi passaggi.
- Scrivere il test utilizzando la struttura "Data", "Quando", "Dopo" e "Con un nome conforme alla convenzione".
- Verifica che la verifica non vada a buon fine.
- Scrivi il codice minimo per superare il test.
- Ripeti l'operazione per tutti i test.
Anziché iniziare a correggere il bug, dovrai iniziare a scrivere i test. Successivamente puoi confermare di avere test che ti proteggeranno dalla reintroduzione accidentale di questi bug in futuro.
- Se è presente un elenco vuoto (
emptyList()
), entrambe le percentuali devono essere 0f. - Se si è verificato un errore durante il caricamento delle attività, l'elenco sarà
null
ed entrambe le percentuali devono essere 0f. - Esegui i test e verifica che non superino:
Passaggio 3. Correggere
Ora che hai le verifiche, correggi il bug.
- Correggi il bug in
getActiveAndCompletedStats
restituendo0f
setasks
ènull
o vuoto:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
return if (tasks == null || tasks.isEmpty()) {
StatsResult(0f, 0f)
} else {
val totalTasks = tasks.size
val numberOfActiveTasks = tasks.count { it.isActive }
StatsResult(
activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
)
}
}
- Esegui nuovamente i test e verifica che vengano superati tutti.
Se segui TDD e scrivi prima i test, hai verificato di:
- Le nuove funzionalità sono sempre associate a test, pertanto i test agiscono da documentazione su ciò che fa il tuo codice.
- I tuoi test verificano la correttezza dei risultati e proteggono da bug che hai già visto.
Soluzione: scrivere più test
Di seguito sono riportati tutti i test e il codice funzione corrispondente.
StatisticheUtils.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
val tasks = listOf(
Task("title", "desc", isCompleted = false)
)
// When the list of tasks is computed with an active task
val result = getActiveAndCompletedStats(tasks)
// Then the percentages are 100 and 0
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
@Test
fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
val tasks = listOf(
Task("title", "desc", isCompleted = true)
)
// When the list of tasks is computed with a completed task
val result = getActiveAndCompletedStats(tasks)
// Then the percentages are 0 and 100
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(100f))
}
@Test
fun getActiveAndCompletedStats_both_returnsFortySixty() {
// Given 3 completed tasks and 2 active tasks
val tasks = listOf(
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = false),
Task("title", "desc", isCompleted = false)
)
// When the list of tasks is computed
val result = getActiveAndCompletedStats(tasks)
// Then the result is 40-60
assertThat(result.activeTasksPercent, `is`(40f))
assertThat(result.completedTasksPercent, `is`(60f))
}
@Test
fun getActiveAndCompletedStats_error_returnsZeros() {
// When there's an error loading stats
val result = getActiveAndCompletedStats(null)
// Both active and completed tasks are 0
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(0f))
}
@Test
fun getActiveAndCompletedStats_empty_returnsZeros() {
// When there are no tasks
val result = getActiveAndCompletedStats(emptyList())
// Both active and completed tasks are 0
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
Statistiche Util.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
return if (tasks == null || tasks.isEmpty()) {
StatsResult(0f, 0f)
} else {
val totalTasks = tasks.size
val numberOfActiveTasks = tasks.count { it.isActive }
StatsResult(
activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
)
}
}
Ottimo lavoro con le nozioni di base sulla scrittura e sull'esecuzione dei test. Dopodiché imparerai a scrivere test di base ViewModel
e LiveData
.
Nel resto del codelab, imparerai a scrivere test per due classi Android comuni nella maggior parte delle app: ViewModel
e LiveData
.
Inizi scrivendo i test per TasksViewModel
.
Ci occuperai dei test che hanno tutte la logica nel modello di visualizzazione e non si basano sul codice del repository. Il codice del repository prevede codice asincrono, database e chiamate di rete, che aggiungono complessità al test. Evitarai per il momento di concentrarti sulla scrittura di test per la funzionalità ViewModel che non testerà direttamente nulla nel repository.
Il test che scriverai verificherà che, quando chiami il metodo addNewTask
, il Event
per l'apertura della nuova finestra dell'attività viene attivato. Ecco il codice dell'app che proverai.
TasksViewModel.kt
fun addNewTask() {
_newTaskEvent.value = Event(Unit)
}
Passaggio 1. Creare una classe TasksViewModelTest
Seguendo gli stessi passaggi descritti per StatisticsUtilTest
, in questo passaggio creerai un file di test per TasksViewModelTest
.
- Apri il corso che vuoi testare nel pacchetto
tasks
,TasksViewModel.
- Nel codice, fai clic con il pulsante destro del mouse sul nome del corso
TasksViewModel
-> Generate -> Test.
- Nella schermata Create Test (Crea test), fai clic su OK per accettare (non dovrai modificare alcuna impostazione predefinita).
- Nella finestra di dialogo Choose Directory Directory (Scegli directory di destinazione), scegli la directory test.
Passaggio 2. Inizia a scrivere il test del modello di visualizzazione
In questo passaggio aggiungi un test del modello di visualizzazione per verificare che, quando chiami il metodo addNewTask
, viene attivato l'elemento Event
per aprire la nuova finestra di attività.
- Crea un nuovo test chiamato
addNewTask_setsNewTaskEvent
.
TasksViewModelTest.kt
class TasksViewModelTest {
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh TasksViewModel
// When adding a new task
// Then the new task event is triggered
}
}
Cosa succede con il contesto delle applicazioni?
Quando crei un'istanza di TasksViewModel
per testare, il suo costruttore richiede un contesto delle applicazioni. Ma in questo test non stai creando un'applicazione completa con attività, UI e frammenti, quindi come puoi ottenere un contesto per l'applicazione?
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(???)
Le librerie di test AndroidX includono classi e metodi che ti forniscono versioni dei componenti come applicazioni e attività destinate ai test. Quando disponi di un test locale in cui ti servono classi di framework Android simulate(come il contesto di un'applicazione), segui questi passaggi per configurare correttamente AndroidX Test:
- Aggiungi le dipendenze di base e delle estensioni AndroidX Test
- Aggiungere la dipendenza Robolectric Testing Library
- Annota la classe con il runner per AndroidJunit4
- Scrivere il codice di test AndroidX
Completa questi passaggi per poi capire come funzionano insieme.
Passaggio 3. Aggiungi le dipendenze del Gradle
- Copia queste dipendenze nel file
build.gradle
del modulo della tua app per aggiungere le dipendenze principali dell'esperimento AndroidX di base, nonché la dipendenza Robolectric test.
app/build.gradle
// AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
Passaggio 4. Aggiunta esecutore test JUnit
- Aggiungi
@RunWith(AndroidJUnit4::class)
sopra la classe del test.
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
Passaggio 5. Usa AndroidX Test
A questo punto, puoi utilizzare la libreria di test di AndroidX. Viene incluso il metodo ApplicationProvider.getApplicationContex
t
, che riceve un contesto applicazione.
- Crea un elemento
TasksViewModel
conApplicationProvider.getApplicationContext()
dalla libreria di test di AndroidX.
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- Chiama
addNewTask
al numerotasksViewModel
.
TasksViewModelTest.kt
tasksViewModel.addNewTask()
A questo punto, il test dovrebbe essere simile al seguente codice.
TasksViewModelTest.kt
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
// TODO test LiveData
}
- Esegui il test per confermare che funzioni.
Concetto: come funziona AndroidX Test?
Che cos'è AndroidX Test?
AndroidX Test è una raccolta di librerie per i test. Include classi e metodi che ti offrono versioni di componenti come applicazioni e attività, pensati per i test. Ad esempio, questo codice è un esempio di una funzione di test di AndroidX per ottenere un contesto di applicazione.
ApplicationProvider.getApplicationContext()
Uno dei vantaggi delle API AndroidX Test è che sono progettati per funzionare sia per i test locali sia per i test strumentati. Questo perché:
- Puoi eseguire lo stesso test di un test locale o di una strumentazione.
- Non è necessario imparare diverse API di test per i test locali e con strumento.
Ad esempio, perché hai scritto il codice con le librerie di test di AndroidX, puoi spostare la classe TasksViewModelTest
dalla cartella test
alla cartella androidTest
e i test verranno comunque eseguiti. Il getApplicationContext()
funziona in modo leggermente diverso a seconda che sia eseguito come test locale o con strumento:
- Se è un test con strumentazione, il contesto dell'applicazione viene fornito all'avvio di un emulatore o tramite un dispositivo reale.
- Se si tratta di un test locale, viene utilizzato un ambiente Android simulato.
Che cos'è Robolectric?
L'ambiente Android simulato utilizzato da AndroidX Test per i test locali è fornito da Robolectric. Robolectric è una libreria che crea un ambiente Android simulato per i test e viene eseguita più velocemente rispetto all'avvio di un emulatore o all'esecuzione su un dispositivo. Senza la dipendenza Robolectric, verrà visualizzato questo errore:
A cosa serve @RunWith(AndroidJUnit4::class)
?
Un test runner è un componente di JUnit che esegue i test. Senza un esecutore, i test non verrebbero eseguiti. C'è un runner predefinito fornito da JUnit che ricevi automaticamente. @RunWith
sostituisce il runner predefinito.
L'esecutore del test di AndroidJUnit4
consente ad AndroidX Test di eseguire il test in modo diverso, a seconda del fatto che siano testati con la strumentazione o test locali.
Passaggio 6. Correggi gli avvisi Robolectric
Quando esegui il codice, nota che viene utilizzato Robolectric.
Grazie ad AndroidX Test e al runner per AndroidJunit4, è possibile farlo senza che tu debba scrivere direttamente una sola riga di codice Robolectric.
Potresti notare due avvisi.
No such manifest file: ./AndroidManifest.xml
"WARN: Android SDK 29 requires Java 9..."
Puoi correggere l'avviso No such manifest file: ./AndroidManifest.xml
aggiornando il file gradle.
- Aggiungi la seguente riga al file Gradle in modo che venga utilizzato il file manifest Android corretto. L'opzione includeAndroidResources ti consente di accedere alle risorse Android nei test delle unità, incluso il file AndroidManifest.
app/build.gradle
// Always show the result of every unit test when running via command line, even if it passes.
testOptions.unitTests {
includeAndroidResources = true
// ...
}
L'avviso "WARN: Android SDK 29 requires Java 9..."
è più complicato. L'esecuzione dei test su Android Q richiede Java 9. Anziché provare a configurare Android Studio in modo da utilizzare Java 9, per il codelab mantieni il target e compila l'SDK a 28.
In sintesi:
- I test del modello di visualizzazione pura possono in genere essere effettuati nel set di sorgenti
test
perché il loro codice di solito non richiede Android. - Puoi utilizzare il test di AndroidXdella libreria per ottenere versioni di test di componenti come Applicazioni e attività.
- Se devi eseguire codice Android simulato nel set di origini
test
, puoi aggiungere la dipendenza Robolectric e l'annotazione@RunWith(AndroidJUnit4::class)
.
Congratulazioni, stai utilizzando la libreria di test di AndroidX e Robolectric per eseguire un test. Il tuo test non è terminato (non hai ancora scritto un'affermazione, ora si tratta solo di // TODO test LiveData
). Imparerai a scrivere le dichiarazioni rivendicate con LiveData
in seguito.
In questa attività, imparerai come rivendicare correttamente il valore LiveData
.
Ecco da dove avevi interrotto addNewTask_setsNewTaskEvent
per visualizzare il test del modello.
TasksViewModelTest.kt
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
// TODO test LiveData
}
Per testare LiveData
, ti consigliamo di svolgere due operazioni:
- Usa
InstantTaskExecutorRule
- Assicurati che
LiveData
osservazione
Passaggio 1. Usa InstantTaskExecutorRule
InstantTaskExecutorRule
è una regola JUnit. Quando lo utilizzi con l'annotazione @get:Rule
, viene eseguito parte del codice nella classe InstantTaskExecutorRule
prima e dopo i test (per vedere il codice esatto, puoi usare la scorciatoia da tastiera Comando+B per visualizzare il file).
Questa regola esegue tutti i job in background relativi ai componenti dell'architettura nello stesso thread in modo che i risultati del test vengano eseguiti in modo sincrono e in un ordine ripetibile. Quando scrivi test che includono test in tempo reale, utilizza questa regola.
- Aggiungi la dipendenza Gradle per la Libreria di test dei componenti dell'architettura (che contiene questa regola).
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- Apri
TasksViewModelTest.kt
- Aggiungi
InstantTaskExecutorRule
all'interno della classeTasksViewModelTest
.
TasksViewModelTest.kt
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Other code...
}
Passaggio 2. Aggiungi la classe LiveDataTestUtil.kt
Il passaggio successivo consiste nell'assicurarti di eseguire l'esame del LiveData
.
Quando utilizzi LiveData
, in genere hai un'attività o un frammento (LifecycleOwner
) osserva il LiveData
.
viewModel.resultLiveData.observe(fragment, Observer {
// Observer code here
})
Questa osservazione è importante. Devi avere osservatori attivi su LiveData
per
- attivare qualsiasi evento
onChanged
. - attivare qualsiasi Trasformazione.
Per ottenere il comportamento LiveData
previsto per il modello di vista LiveData
, devi osservare il LiveData
con un LifecycleOwner
.
Questo rappresenta un problema: nel test TasksViewModel
non hai attività o frammenti per osservare il tuo LiveData
. Per aggirare il problema, puoi utilizzare il metodo observeForever
, che garantisce che LiveData
venga costantemente osservato, senza bisogno di una LifecycleOwner
. Quando observeForever
, devi ricordarti di rimuovere la persona che la osserva o di rischiare una fuga di dati.
Il codice è simile al seguente. Esaminalo:
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// Create observer - no need for it to do anything!
val observer = Observer<Event<Unit>> {}
try {
// Observe the LiveData forever
tasksViewModel.newTaskEvent.observeForever(observer)
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.value
assertThat(value?.getContentIfNotHandled(), (not(nullValue())))
} finally {
// Whatever happens, don't forget to remove the observer!
tasksViewModel.newTaskEvent.removeObserver(observer)
}
}
C'è molto codice boilerplate per osservare un singolo LiveData
in un test. Esistono diversi modi per eliminare questa caldaia. Creerai una funzione di estensione chiamata LiveDataTestUtil
per semplificare l'aggiunta degli osservatori.
- Crea un nuovo file Kotlin denominato
LiveDataTestUtil.kt
nel tuo set di originitest
.
- Copia e incolla il codice qui sotto.
LiveDataTestUtil.kt
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
@Suppress("UNCHECKED_CAST")
return data as T
}
Si tratta di un metodo piuttosto complicato. Crea una funzione di estensione Kotlin denominata getOrAwaitValue
che aggiunge un osservatore, ottiene il valore LiveData
e quindi ripulisce l'osservatore, in pratica una breve versione riutilizzabile del codice observeForever
mostrato sopra. Per una spiegazione completa di questo corso, consulta questo post del blog.
Passaggio 3. Usare getOrAwaitValue per scrivere l'asserzione
In questo passaggio, devi utilizzare il metodo getOrAwaitValue
e scrivere un'istruzione di dichiarazione che verifichi che newTaskEvent
è stato attivato.
- Ricevi il valore
LiveData
pernewTaskEvent
utilizzandogetOrAwaitValue
.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- Dichiara che il valore non sia null.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))
Il test completo dovrebbe avere il seguente codice.
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
assertThat(value.getContentIfNotHandled(), not(nullValue()))
}
}
- Esegui il codice e guarda il pass di prova.
Ora che hai esaminato come scrivere un test, creane uno in autonomia. In questo passaggio, utilizzando le competenze apprese, fai pratica con la scrittura di un altro test TasksViewModel
.
Passaggio 1. Scrivi il tuo test di ViewModel
Dovrai scrivere setFilterAllTasks_tasksAddViewVisible()
. Questo test dovrebbe verificare che, se hai impostato il tipo di filtro in modo che mostri tutte le attività, che il pulsante Aggiungi attività sia visibile.
- Utilizzando
addNewTask_setsNewTaskEvent()
come riferimento, scrivi un test inTasksViewModelTest
denominatosetFilterAllTasks_tasksAddViewVisible()
che imposti la modalità filtro suALL_TASKS
e dichiari chetasksAddViewVisible
LiveData ètrue
.
Usa il codice qui sotto per iniziare.
TasksViewModelTest
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
// When the filter type is ALL_TASKS
// Then the "Add task" action is visible
}
Nota:
- L'enumerazione di
TasksFilterType
per tutte le attività èALL_TASKS.
- La visibilità del pulsante per aggiungere un'attività dipende dalla
tasksAddViewVisible.
diLiveData
- Esegui il test.
Passaggio 2. Confronta il tuo test con la soluzione
Confronta la tua soluzione con la seguente.
TasksViewModelTest
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
}
Verifica se:
- Il tuo
tasksViewModel
viene creato utilizzando la stessa istruzioneApplicationProvider.getApplicationContext()
di AndroidX. - Viene chiamato il metodo
setFiltering
, passando l'enumerazione del tipo di filtroALL_TASKS
. - Puoi verificare che il valore di
tasksAddViewVisible
sia true utilizzando il metodogetOrAwaitNextValue
.
Passaggio 3. Aggiungere una regola @before
Nota che all'inizio di entrambi i test definisci un TasksViewModel
.
TasksViewModelTest
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
Quando hai ripetuto il codice di configurazione per più test, puoi utilizzare l'annotazione @before per creare un metodo di configurazione e rimuovere il codice ripetuto. Dal momento che tutti questi test testeranno il TasksViewModel
e saranno necessari un modello di visualizzazione, sposta questo codice in un blocco @Before
.
- Crea una variabile di istanza
lateinit
denominatatasksViewModel|
. - Crea un metodo
setupViewModel
. - Annotalo con
@Before
. - Sposta il codice di creazione dell'istanza di un modello di visualizzazione in
setupViewModel
.
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- Esegui il codice.
Avviso
Non fare quanto segue, non inizializzare
tasksViewModel
e la relativa definizione:
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
In questo modo, la stessa istanza verrà utilizzata per tutti i test. Si tratta di un comportamento da evitare perché ogni test deve avere una nuova istanza dell'argomento in fase di test (in questo caso, il ViewView).
Il codice finale per TasksViewModelTest
dovrebbe essere simile al seguente codice.
TasksViewModelTest
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
@Test
fun addNewTask_setsNewTaskEvent() {
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.awaitNextValue()
assertThat(
value?.getContentIfNotHandled(), (not(nullValue()))
)
}
@Test
fun getTasksAddViewVisible() {
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
}
}
Fai clic qui per visualizzare una differenza tra il codice che hai iniziato e quello finale.
Per scaricare il codice per il codelab finito, puoi utilizzare il comando git riportato di seguito:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
In alternativa, puoi scaricare il repository come file ZIP, decomprimerlo e aprirlo in Android Studio.
Questo codelab ha trattato:
- Come eseguire test da Android Studio.
- Differenza tra test locali (
test
) e test di strumentazione (androidTest
). - Come scrivere test delle unità locali utilizzando JUnit e Hamcrest.
- Configurazione di test ViewModel con la libreria di test di AndroidX.
Corso Udacity:
Documentazione per gli sviluppatori Android:
- Guida all'architettura delle app
- JUnit4
- Hamcrest
- Libreria test robotectrici
- Libreria di test di AndroidX
- Libreria di test per i componenti dell'architettura AndroidX
- set di origini
- Testa dalla riga di comando
Video:
Altro:
Per i link ad altri codelab in questo corso, consulta la pagina di destinazione Advanced Android in Kotlin.