Strategie di test

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

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

  • Rileva i problemi il prima possibile.
  • Si esegue rapidamente.
  • Fornisce indicazioni chiare quando è necessario correggere qualcosa.

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

La piramide di test

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

Per *fideltà si intende la somiglianza tra l'ambiente di runtime di test e l'ambiente di produzione.

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

La maggior parte delle app dovrebbe avere molti test piccoli e relativamente pochi test grandi. 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 più grandi e meno numerosi che formano la punta.

Riduci al minimo il costo di un bug

Una buona strategia di test massimizza la produttività degli sviluppatori riducendo al minimo il costo di rilevamento dei bug.

Consideriamo un esempio di strategia potenzialmente inefficiente. Qui, il numero di test per dimensione non è organizzato in una piramide. Esistono troppi test end-to-end di grandi dimensioni e troppo pochi test dell'interfaccia utente dei componenti:

Una strategia incentrata sui test manuali, in cui molti test vengono eseguiti manualmente e i test dei dispositivi vengono eseguiti solo ogni notte.
Figura 2. Una strategia incentrata sui test manuali, in cui molti test vengono eseguiti manualmente e i test dei dispositivi vengono eseguiti solo ogni notte.

Ciò significa che vengono eseguiti troppo pochi test prima dell'unione. Se è presente un bug, i test potrebbero non rilevarlo finché non vengono eseguiti i test end-to-end giornalieri o settimanali.

È importante considerare le implicazioni di questo aspetto per il costo di identificazione e 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 delle unità, di solito viene risolto 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ù elevato.

Detto questo, una strategia di test inefficiente è meglio di nessuna strategia. Quando un bug arriva in produzione, la correzione richiede molto tempo per arrivare ai dispositivi dell'utente, 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 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 release candidate, in ordine crescente.
Figura 3. Una piramide di test a 5 livelli.
  • Un test di unità viene eseguito sulla macchina host e verifica una singola unità di logica funzionale senza dipendenze dal framework Android.
    • Esempio: verificare gli errori uno alla volta in una funzione matematica.
  • Un test di componente verifica la funzionalità o l'aspetto di un modulo o di un componente indipendentemente dagli altri componenti del sistema. A differenza dei test di unità, la superficie di un test di componente si estende ad astrazioni superiori rispetto ai 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 solitamente vengono eseguiti a livello di funzionalità.
  • Un test di applicazione verifica la funzionalità dell'intera applicazione sotto forma di programma binario di cui è possibile eseguire il deployment. Si tratta di test di integrazione di grandi dimensioni che utilizzano un file binario di cui è possibile eseguire il debug, ad esempio una build di sviluppo che può contenere hook di test, come sistema in test.
    • Esempio: test del comportamento dell'interfaccia utente per verificare le modifiche alla configurazione in un folding, test di localizzazione e accessibilità
  • Un test candidato alla release verifica la funzionalità di una build di release. Sono simili ai test delle applicazioni, tranne per il fatto che il file binario dell'applicazione è minimizzato e ottimizzato. Si tratta di test di integrazione end-to-end di grandi dimensioni eseguiti in un ambiente il più vicino possibile alla produzione, senza esporre l'app ad account utente pubblici o backend pubblici.

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

Ambito

Accesso alla rete

Esecuzione

Tipo di build

Ciclo di vita

Unità

Metodo o classe singolo con dipendenze minime.

No

Locale

Sottoponibile a debug

Pre-unione

Componente

A livello di modulo o componente

Più corsi insieme

No

Emulatore
Robolectric
locale

Sottoponibile a debug

Prima dell'unione

Funzionalità

A livello di funzionalità

Integrazione con componenti di proprietà di altri team

Simulato


Robolectric
Emulatore
Dispositivi locale

Di cui è possibile eseguire il debug

Pre-unione

Applicazione

A livello di applicazione

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

Simulato
Server di staging
Server di produzione

Emulatore
Dispositivi

Di cui è possibile eseguire il debug

Pre-unione
Dopo l'unione

Release Candidate

A livello di applicazione

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

Server di produzione

Emulatore
Dispositivi

Build di release compressa

Post-unione
Pre-release

Decidi la categoria di prova

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à: l'interfaccia utente di un flusso di accesso. A seconda di ciò che devi testare, sceglierai categorie diverse:

Oggetto del test

Descrizione di ciò che viene testato

Categoria di test

Tipo di test di esempio

Logica di convalida dei moduli

Una classe che convalida l'indirizzo email in base a un'espressione regolare e controlla che il campo della password sia stato inserito. Non ha dipendenze.

Test delle unità

Test di unità JVM locale

Comportamento dell'interfaccia utente del modulo di accesso

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

Test dei componenti

Test del comportamento dell'interfaccia utente eseguito su Robolectric

Aspetto dell'interfaccia utente del modulo di accesso

Un modulo che segue una specifica UX

Test dei componenti

Test di screenshot dell'anteprima di composizione

Integrazione con il gestore dell'autenticazione

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

Test delle funzionalità

Test JVM con falsi

Finestra di dialogo di accesso

Schermata che mostra il modulo di accesso quando viene premuto il pulsante di accesso.

Test delle applicazioni

Test del comportamento dell'interfaccia utente eseguito su Robolectric

Percorso dell'utente fondamentale: accesso

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

Candidato per la release

Test del comportamento dell'interfaccia utente di Compose end-to-end 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 il costo dell'infrastruttura, l'instabilità e i tempi di test lunghi.

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

Anche i test manuali possono essere parte della tua strategia di test. In genere, i team di QA svolgono test di release candidate, ma possono essere coinvolti anche in altre fasi. Ad esempio, test esplorativi per i 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 i test. Ad esempio, seguendo il modello a 5 livelli:

Categoria

Ambiente (dove)

Attivazione (quando)

Unità

[Locale][4]

Ogni commit

Componente

Locale

Ogni commit

Funzionalità

Locali ed emulatori

Prima dell'unione, prima di unire o inviare una modifica

Applicazione

Local, emulatori, 1 smartphone, 1 pieghevole

Dopo l'unione, dopo l'unione 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 dell'unione o dell'invio di una modifica.
  • I test dell'applicazione vengono eseguiti dopo l'unione.
  • I test dei candidati al rilascio vengono eseguiti ogni notte su smartphone, pieghevoli e 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 su una cadenza giornaliera, potresti ridurre i tempi di compilazione e di test del CI, ma potresti anche prolungare il ciclo di feedback.