Utilizzare il doppio del test in Android

Quando si progetta la strategia di test per un elemento o un sistema, ci sono tre aspetti dei test correlati:

  • Ambito: quanta parte del codice tocca il test? I test consentono di verificare una singola l'intera applicazione o una via di mezzo. La l'ambito testato è in corso di test e in genere viene indicato come Oggetto Test, anche lo stato Sistema in fase di test o Unità in fase di test.
  • Velocità: quanto è veloce il test? Le velocità di test possono variare da millisecondi diversi minuti.
  • Fedeltà: come è "reale" il test? Ad esempio, se parte del codice per effettuare una richiesta di rete, il codice di test effettua questa richiesta di rete o il risultato non è falso? Se il test dà esito positivo con la rete, ciò significa che ha una maggiore fedeltà. Lo svantaggio è che l'esecuzione del test potrebbe richiedere più tempo, causare errori se la rete non è disponibile oppure potrebbe essere costoso da utilizzare.

Scopri cosa testare per scoprire come iniziare a definire la tua strategia di test.

Isolamento e dipendenze

Quando testi un elemento o un sistema di elementi, lo fai isolando. Per Ad esempio, per testare un ViewModel non è necessario avviare un emulatore e avviare una UI perché non dipende (o non dovrebbe) dipendere dal framework Android.

Tuttavia, il soggetto sottoposto a test potrebbe dipendere da altri perché funzioni. Per un'istanza ViewModel potrebbe dipendere da un repository di dati per funzionare.

Quando è necessario fornire una dipendenza a un soggetto sottoposto a test, una pratica comune consiste nel creare un oggetto test double (o oggetto test). I test doppi sono oggetti hanno l'aspetto e le funzionalità dell'app, ma vengono creati nel test per per fornire un comportamento o dati specifici. Il vantaggio principale è che le tue in modo rapido e semplice.

Tipi di test doppi

Esistono vari tipi di test doppi:

Falso Un doppio di prova che ha un "funzionamento" implementazione della classe, ma è implementata in un modo che lo rende adatto per i test ma inadatto per la produzione.

Esempio: un database in memoria.

I falsi non richiedono un framework simulato e sono leggeri. Sono preferiti.

Simulazione Un doppio test che si comporta come lo programma e che ha aspettative sulle sue interazioni. Le simulazioni non supereranno i test se le loro interazioni non corrispondono ai requisiti da te definiti. A questo scopo, in genere le simulazioni vengono create con un framework di simulazione.

Esempio: verificare che un metodo in un database sia stato chiamato esattamente una volta.

Stub Un doppio del test che si comporta come lo programma, ma che non ha aspettative sulle sue interazioni. Di solito viene creato con un framework simulato. Per semplicità, i falsi sono da preferire agli stub.
dummy Un doppio di test che viene fatto passare ma non utilizzato, ad esempio se devi solo fornirlo come parametro.

Esempio: una funzione vuota passata come callback dei clic.

Spionaggio Un wrapper su un oggetto reale che tiene traccia anche di alcune informazioni aggiuntive, come nelle simulazioni. Di solito vengono evitati per aggiungere complessità. Le falsità o le truffe sono quindi da preferire alle spie.
Ombre Falsa utilizzata in Robolectric.

Esempio di utilizzo di un falso

Supponiamo di voler eseguire il test delle unità per un ViewModel che dipende da un'interfaccia denominato UserRepository ed espone il nome del primo utente a una UI. Puoi creare un falso test doppio implementando l'interfaccia e restituendo dati e i dati di Google Cloud.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

Questo UserRepository fittizio non deve dipendere dai dati locali e remoti le origini utilizzate dalla versione di produzione. Il file si trova nell'origine di test impostato e non verrà distribuito con l'app di produzione.

Una dipendenza falsa può restituire dati noti senza dipendere da origini dati remote
Figura 1: una dipendenza falsa in un test delle unità.

Il seguente test verifica che ViewModel espone correttamente il primo utente alla vista.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

Sostituire UserRepository con un falso è facile in un test delle unità, perché ViewModel viene creato dal tester. Tuttavia, può essere difficile sostituire elementi arbitrari nei test più grandi.

Sostituzione dei componenti e inserimento delle dipendenze

Quando i test non hanno alcun controllo sulla creazione dei sistemi sottoposti a test, la sostituzione dei componenti per i test doppi diventa più coinvolta e richiede dell'app in modo che segua un design testabile.

Anche i test end-to-end di grandi dimensioni possono trarre vantaggio dall'utilizzo di test doppi, come test dell'interfaccia utente con strumenti che analizza l'intero flusso di utenti nella tua app. Nella in questo caso, potresti rendere il test ermetico. Un test ermetico evita tutte le dipendenze esterne, come il recupero dei dati da internet. Questo migliora l'affidabilità e le prestazioni.

Figura 2: un grande test che copre la maggior parte dell'app e falsifica i dati remoti.

Puoi progettare l'app in modo da ottenere questa flessibilità manualmente, ma ti consigliamo di Utilizzando un framework di inserimento delle dipendenze come Hilt per sostituire i componenti. nella tua app al momento del test. Consulta la Guida al test di Hilt.

Robolettrico

Su Android, puoi utilizzare il framework Robolectric, che offre un tipo speciale di test doppio. Robolectric ti consente di eseguire i test su sulla workstation o nell'ambiente di integrazione continua. Utilizza un una JVM standard, senza emulatore o dispositivo. Simula l'inflazione delle visualizzazioni il caricamento delle risorse e altre parti del framework Android con test doppi chiamate ombre.

Robolectric è un simulatore, quindi non dovrebbe sostituire semplici test delle unità né essere utilizzato per eseguire test di compatibilità. Garantisce velocità e riduce i costi a scapito a una fedeltà inferiore in alcuni casi. Un buon approccio per i test dell'interfaccia utente consiste nel farli compatibile sia con i test robotici sia con quelli strumentati e decidere quando eseguirli a seconda della necessità di testare la funzionalità o la compatibilità. Entrambi espresso e Compose possono essere eseguiti su Robolectric.