Strategie di test

I test automatizzati ti aiutano a migliorare la qualità dell'app in diversi modi. Ad esempio, ti aiuta a eseguire la convalida, rilevare le regressioni e verificare la compatibilità. Una buona strategia di test ti consente di sfruttare i test automatizzati per concentrarti su un vantaggio importante: la produttività degli sviluppatori.

I team raggiungono livelli di produttività più elevati quando utilizzano un approccio sistematico al test, abbinato a miglioramenti dell'infrastruttura. In questo modo, riceverai tempestivamente un feedback sul comportamento del codice. Una buona strategia di test:

  • Rileva i problemi il prima possibile.
  • Esegue rapidamente.
  • Fornisce indicazioni chiare quando è necessario risolvere un problema.

Questa pagina ti aiuterà a decidere quali tipi di test implementare, dove eseguirli e con quale frequenza.

La piramide dei test

Puoi classificare i test nelle applicazioni moderne in base alle dimensioni. I test di piccole dimensioni si concentrano solo su una piccola parte di codice, il che li rende veloci e affidabili. I test di grandi dimensioni hanno un ambito ampio e richiedono configurazioni più complesse e difficili da gestire. Tuttavia, i test di grandi dimensioni hanno una fedeltà maggiore* e possono rilevare molti più problemi in una sola volta.

*Fidelity si riferisce alla somiglianza dell'ambiente di runtime di test con l'ambiente di produzione.

La distribuzione del numero di test per ambito viene in genere visualizzata in una piramide.
Figura 1. La distribuzione del numero di test per ambito viene in genere visualizzata in una piramide.

La maggior parte delle app dovrebbe avere molti test di piccole dimensioni e relativamente pochi test di grandi dimensioni. La distribuzione dei test in ogni categoria deve formare una piramide, con i test più numerosi di piccole dimensioni che formano la base e i test meno numerosi di grandi dimensioni che formano la punta.

Ridurre al minimo il costo di un bug

Una buona strategia di test massimizza la produttività degli sviluppatori e riduce al minimo il costo della ricerca di bug.

Prendiamo in considerazione un esempio di strategia potenzialmente inefficiente. In questo caso, il numero di test per dimensione non è organizzato in una piramide. Ci sono troppi test end-to-end e troppo pochi test dell'interfaccia utente dei componenti:

Una strategia con un numero elevato di test eseguiti manualmente e i test del dispositivo eseguiti solo di notte.
Figura 2. Una strategia con un numero elevato di test eseguiti manualmente e i test del dispositivo eseguiti solo di notte.

Ciò significa che vengono eseguiti troppo pochi test prima dell'unione. Se è presente un bug, i test potrebbero non rilevarlo fino all'esecuzione dei test end-to-end notturni o settimanali.

È importante considerare le implicazioni di questo aspetto sul costo dell'identificazione e della correzione dei bug e perché è importante orientare le attività di test verso test più piccoli e frequenti:

  • Quando il bug viene rilevato da un test unitario, in genere viene corretto in pochi minuti, quindi il costo è basso.
  • Un test end-to-end potrebbe richiedere giorni per scoprire lo stesso bug. Ciò potrebbe coinvolgere più membri del team, riducendo la produttività complessiva e potenzialmente ritardando una release. Il costo di questo bug è più alto.

Detto questo, una strategia di test inefficiente è meglio di nessuna strategia. Quando un bug viene rilasciato in produzione, la correzione richiede molto tempo per essere implementata nei dispositivi degli utenti, a volte settimane, quindi il ciclo di feedback è il più lungo e costoso.

Una strategia di test scalabile

La piramide dei test è tradizionalmente suddivisa in tre categorie:

  • Test delle unità
  • Test di integrazione
  • Test end-to-end.

Tuttavia, questi concetti non hanno definizioni precise, quindi i team potrebbero voler definire le proprie categorie in modo diverso, ad esempio utilizzando 5 livelli:

Una piramide di test a 5 livelli con le categorie test delle unità, test dei componenti, test delle funzionalità, test delle applicazioni e test delle versioni candidate, in ordine crescente.
Figura 3. Una piramide di test a 5 livelli.
  • Un test unitario viene eseguito sul computer host e verifica una singola unità funzionale di logica senza dipendenze dal framework Android.
    • Esempio: verifica degli errori off-by-one in una funzione matematica.
  • Un test dei componenti verifica la funzionalità o l'aspetto di un modulo o componente indipendentemente dagli altri componenti del sistema. A differenza dei test delle unità, l'area di superficie di un test dei componenti si estende ad astrazioni superiori al di sopra di singoli metodi e classi.
  • Un test delle funzionalità verifica l'interazione di due o più componenti o moduli indipendenti. I test delle funzionalità sono più grandi e complessi e di solito operano a livello di funzionalità.
  • Un test dell'applicazione verifica la funzionalità dell'intera applicazione sotto forma di binario implementabile. Si tratta di test di integrazione di grandi dimensioni che utilizzano un binario di cui è possibile eseguire il debug, ad esempio una build di sviluppo che può contenere hook di test, come sistema in fase di test.
    • Esempio: test del comportamento dell'interfaccia utente per verificare le modifiche alla configurazione in un test pieghevole, di localizzazione e di accessibilità
  • Il test di una release candidate verifica la funzionalità di una build di rilascio. Sono simili ai test dell'applicazione, tranne per il fatto che il binario dell'applicazione è ridotto e ottimizzato. Si tratta di test di integrazione end-to-end di grandi dimensioni che vengono eseguiti in un ambiente il più vicino possibile alla produzione senza esporre l'app a account utente pubblici o backend pubblici.

Questa classificazione tiene conto di fedeltà, tempo, ambito e livello di isolamento. Puoi avere diversi tipi di test su più livelli. Ad esempio, il livello di test dell'applicazione può contenere test di comportamento, screenshot e prestazioni.

Ambito

Accesso di rete

Esecuzione

Tipo di build

Ciclo di vita

Unità

Metodo o classe singoli con dipendenze minime.

No

Locale

Debuggable

Pre-merge

Componente

Livello di modulo o componente

Più corsi insieme

No

Locale
Robolectric
Emulatore

Debuggable

Pre-merge

Funzionalità

Livello della funzionalità

Integrazione con componenti di proprietà di altri team

Mocked

Locale
Robolectric
Emulator
Devices

Debuggable

Pre-merge

Applicazione

Livello di applicazione

Integrazione con funzionalità e/o servizi di proprietà di altri team

Simulato
Server di staging
Server di produzione

Emulator
Devices

Debuggable

Pre-fusione
Post-fusione

Candidato per la release

Livello di applicazione

Integrazione con funzionalità e/o servizi di proprietà di altri team

Server di produzione

Emulator
Devices

Build di release ridotta

Post-unione
Pre-release

Decidere la categoria di test

Come regola generale, devi considerare il livello più basso della piramide che può fornire al team il giusto livello di feedback.

Ad esempio, considera come testare l'implementazione di questa funzionalità: la UI di un flusso di accesso. A seconda di ciò che devi testare, scegli categorie diverse:

Materia in fase di test

Descrizione di ciò che viene testato

Categoria di test

Esempio di tipo di test

Logica di convalida del modulo

Una classe che convalida l'indirizzo email rispetto a un'espressione regolare e verifica che il campo della password sia stato compilato. Non ha dipendenze.

Test delle unità

Test delle unità JVM locali

Comportamento dell'interfaccia utente del modulo di accesso

Un modulo con un pulsante che viene attivato solo dopo la convalida del modulo

Test dei componenti

Test del comportamento dell'interfaccia utente in esecuzione su Robolectric

Aspetto dell'interfaccia utente del modulo di accesso

Un modulo che segue una specifica UX

Test dei componenti

Compose Preview Screenshot test

Integrazione con Auth Manager

L'interfaccia utente che invia le credenziali a un gestore dell'autenticazione e riceve risposte che possono contenere errori diversi.

Test delle funzionalità

Test JVM con dati simulati

Finestra di dialogo di accesso

Una schermata che mostra il modulo di accesso quando viene premuto il pulsante di accesso.

Test delle applicazioni

Test del comportamento dell'interfaccia utente in esecuzione su Robolectric

Critical User Journey: accesso

Un flusso di accesso completo che utilizza un account di test su un server di staging

Candidato per la release

Test end-to-end del comportamento della UI di Compose in esecuzione sul dispositivo

In alcuni casi, l'appartenenza di un elemento a una categoria o a un'altra può essere soggettiva. Esistono altri motivi per cui un test viene spostato verso l'alto o verso il basso, ad esempio costo dell'infrastruttura, instabilità e tempi di test lunghi.

Tieni presente che la categoria di test non determina il tipo di test e non tutte le funzionalità devono essere testate in ogni categoria.

Anche i test manuali possono far parte della tua strategia di test. In genere, i team addetti al QA eseguono test di Release Candidate, ma possono essere coinvolti anche in altre fasi. Ad esempio, test esplorativi per bug in una funzionalità senza uno script.

Infrastruttura di test

Una strategia di test deve essere supportata da infrastrutture e strumenti per aiutare gli sviluppatori a eseguire continuamente i test e applicare regole che garantiscano il superamento di tutti i test.

Puoi classificare i test in base all'ambito per definire quando e dove eseguire quali test. Ad esempio, seguendo il modello a 5 livelli:

Categoria

Ambiente (dove)

Trigger (quando)

Unità

[Locale][4]

Ogni commit

Componente

Locale

Ogni commit

Funzionalità

Locale ed emulatori

Pre-merge, prima di unire o inviare una modifica

Applicazione

Locale, emulatori, 1 smartphone, 1 pieghevole

Post-fusione, dopo la fusione o l'invio di una modifica

Candidato per la release

8 smartphone diversi, 1 pieghevole, 1 tablet

Pre-lancio

  • I test Unit e Component vengono eseguiti sul sistema di integrazione continua per ogni nuovo commit, ma solo per i moduli interessati.
  • Tutti i test Unit, Component e Feature vengono eseguiti prima di unire o inviare una modifica.
  • I test dell'applicazione vengono eseguiti dopo l'unione.
  • I test della Release Candidate vengono eseguiti ogni notte su uno smartphone, un dispositivo pieghevole e un tablet.
  • Prima di una release, i test Release Candidate vengono eseguiti su un numero elevato di dispositivi.

Queste regole possono cambiare nel tempo quando il numero di test influisce sulla produttività. Ad esempio, se sposti i test a una cadenza notturna, potresti ridurre i tempi di compilazione e test dell'integrazione continua, ma potresti anche prolungare il ciclo di feedback.