Utilizzare il doppio del test in Android

Quando testi un elemento o un sistema di elementi, lo fai in isolamento. Ad esempio, per testare un ViewModel non è necessario avviare un emulatore e lanciare un'interfaccia utente perché non dipende (o non dovrebbe dipendere) dal framework Android.

Tuttavia, l'oggetto sottoposto a test potrebbe dipendere da altri per funzionare. Ad esempio, un ViewModel potrebbe dipendere da un repository di dati per funzionare.

Quando devi fornire una dipendenza a un soggetto sottoposto a test, una pratica comune è creare un doppio di test (o un oggetto di test). I sostituti di test sono oggetti che sembrano e si comportano come componenti della tua app, ma vengono creati nel test per fornire un comportamento o dati specifici. I vantaggi principali sono che rendono i test più rapidi e semplici.

Tipi di doppioni di test

Esistono vari tipi di doppioni di test:

Falso Un doppio di test che ha un'implementazione "funzionante" della classe, ma è implementato in modo da essere adatto ai test, ma non alla produzione.

Esempio: un database in memoria.

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

Simulazione Un doppio di test che si comporta come lo programmi e che ha aspettative sulle sue interazioni. Le simulazioni non supereranno i test se le loro interazioni non corrispondono ai requisiti da te definiti. In genere, i mock vengono creati con un framework di simulazione per ottenere tutto questo.

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

Stub Un doppio di test che si comporta come lo programmi, ma non ha aspettative sulle sue interazioni. Di solito viene creato con un framework di simulazione. Per semplicità, i falsi sono da preferire agli stub.
dummy Un doppio di test che viene passato 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. In genere vengono evitati per non aggiungere complessità. Le falsità o le truffe sono quindi da preferire alle spie.
Ombre Fake utilizzato in Robolectric.

Esempio di utilizzo di un falso

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

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

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

Questo UserRepository fittizio non deve dipendere dalle origini dati locali e remote utilizzate dalla versione di produzione. Il file si trova nel set di origine del test e non verrà fornito 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 unitario.

Il seguente test verifica che ViewModel esponga correttamente il primo nome dell'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é il valore ViewModel viene creato dal tester. Tuttavia, può essere difficile sostituire elementi arbitrari in test più grandi.

Sostituzione dei componenti e iniezione di dipendenze

Quando i test non hanno alcun controllo sulla creazione dei sistemi in test, la sostituzione dei componenti con sostituti di test diventa più complessa e richiede che l'architettura dell'app segua un design testabile.

Anche i test end-to-end di grandi dimensioni possono trarre vantaggio dall'utilizzo di test doppi, ad esempio un test della UI strumentato che esplora un flusso utente completo nella tua app. In questo caso, potresti decidere di rendere il test ermetico. Un test ermetico evita tutte le dipendenze esterne, ad esempio il recupero dei dati da internet. Questo migliora l'affidabilità e le prestazioni.

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

Puoi progettare la tua app per ottenere questa flessibilità manualmente, ma ti consigliamo di utilizzare un framework di iniezione di dipendenze come Hilt per sostituire i componenti della tua app al momento del test. Consulta la guida ai test di Hilt.

Passaggi successivi

La pagina Strategie di test mostra come puoi migliorare la produttività utilizzando diversi tipi di test.