Testare o meno, una prospettiva tecnica

Determina cosa devi testare e cosa puoi escludere.

L'articolo precedente ha trattato le nozioni di base dei casi di test e cosa devono contenere. Questo articolo approfondisce la creazione di casi di test da un punto di vista tecnico, illustrando cosa deve essere incluso in ogni test e cosa evitare. In sostanza, scoprirai la risposta alle eterne domande "Che cosa testare" o "Che cosa non testare".

Che cosa testare o meno.

Linee guida generali e pattern

Vale la pena notare che schemi e punti specifici sono fondamentali, indipendentemente dal fatto che tu stia conducendo test di unità, di integrazione o end-to-end. Questi principi possono e devono essere applicati a entrambi i tipi di test, quindi sono un buon punto di partenza.

Semplicità

Quando si tratta di scrivere test, una delle cose più importanti da ricordare è mantenere la semplicità. È importante considerare la capacità del cervello. Il codice di produzione principale occupa molto spazio, lasciando poco spazio per ulteriore complessità. Questo è particolarmente vero per i test.

Se lo spazio disponibile è inferiore, puoi rilassarti un po' durante i test. Ecco perché è fondamentale dare la priorità alla semplicità durante i test. Infatti, le best practice per i test di JavaScript di Yoni Goldberg sottolineano l'importanza della regola d'oro: il test deve essere un assistente e non una formula matematica complessa. In altre parole, dovresti essere in grado di capire l'intento del test a prima vista.

Non rendere i test complessi, non devono essere così.

Devi puntare alla semplicità in tutti i tipi di test, indipendentemente dalla loro complessità. Infatti, più un test è complesso, più è importante semplificarlo. Un modo per ottenere questo risultato è attraverso un design di test piatto, in cui i test vengono mantenuti il più semplici possibile e testare solo ciò che è necessario. Ciò significa che ogni test deve contenere un solo caso di test e che il caso di test deve essere incentrato sul test di una singola funzionalità o funzionalità specifica.

Pensaci da questo punto di vista: dovrebbe essere facile identificare cosa non va quando si legge un test non riuscito. Ecco perché è importante mantenere i test semplici e facili da comprendere. In questo modo, puoi identificare e risolvere rapidamente i problemi quando si verificano.

Testare ciò che vale la pena

Il design dei test semplici favorisce inoltre la concentrazione e contribuisce a garantire che i test siano significativi. Ricorda che non devi creare test solo per la copertura: devono sempre avere uno scopo.

Non testare tutto.

Non testare i dettagli di implementazione

Un problema comune nei test è che spesso questi sono progettati per testare i dettagli di implementazione, ad esempio l'utilizzo di selettori nei componenti o i test end-to-end. I dettagli di implementazione si riferiscono a elementi che in genere gli utenti del tuo codice non useranno, vedranno o conosceranno. Ciò può portare a due problemi principali nei test: falsi negativi e falsi positivi.

I falsi negativi si verificano quando un test non va a buon fine, anche se il codice testato è corretto. Ciò può accadere quando i dettagli di implementazione cambiano a causa di un refactoring del codice dell'applicazione. I falsi positivi si verificano quando un test viene superato, anche se il codice sottoposto a test non è corretto.

Una soluzione a questo problema è prendere in considerazione i diversi tipi di utenti di cui disponi. Gli utenti finali e gli sviluppatori possono avere approcci diversi e interagire con il codice in modo diverso. Quando pianifichi i test, è essenziale considerare ciò che gli utenti vedranno o con cui interagiranno e fare in modo che i test dipendano da questi elementi anziché dai dettagli di implementazione.

Ad esempio, scegliere selettori meno soggetti a modifiche può rendere i test più affidabili: attributi data anziché selettori CSS. Per ulteriori dettagli, consulta Kent C. l'articolo di Dodds su questo argomento o continua a seguirci: a breve pubblicheremo un articolo in merito.

Simulazione: non perdere il controllo

Il mocking è un concetto ampio utilizzato nei test di unità e a volte nei test di integrazione. Consiste nella creazione di dati o componenti falsi per simulare dipendenze che hanno il controllo completo sull'applicazione. In questo modo è possibile eseguire test isolati.

L'utilizzo di simulazioni nei test può migliorare la prevedibilità, la separazione degli aspetti e il rendimento. Inoltre, se devi eseguire un test che richiede l'intervento di una persona (ad esempio la verifica del passaporto), dovrai nasconderlo utilizzando un test simulato. Per tutti questi motivi, i mock sono uno strumento prezioso da prendere in considerazione.

Allo stesso tempo, i mock potrebbero influire sull'accuratezza del test perché non sono esperienze utente reali. Pertanto, devi prestare attenzione quando utilizzi simulazioni e stub.

Dovresti eseguire il test di simulazione nei test end-to-end?

In generale, no. Tuttavia, a volte il mocking può essere un salvavita, quindi non escludiamolo del tutto.

Immagina questo scenario: stai scrivendo un test per una funzionalità che coinvolge un servizio di fornitori di servizi di pagamento di terze parti. Ti trovi in un ambiente sandbox fornito da Google, il che significa che non vengono effettuate transazioni reali. Purtroppo, la sandbox non funziona correttamente, causando il fallimento dei test. La correzione deve essere eseguita dal fornitore di servizi di pagamento. Non puoi fare altro che attendere che il problema venga risolto dal fornitore.

In questo caso, potrebbe essere più vantaggioso ridurre la dipendenza dai servizi che non puoi controllare. Tuttavia, ti consigliamo di utilizzare il mocking con attenzione nei test di integrazione o end-to-end, in quanto riduce il livello di confidenza dei test.

Dettagli dei test: cosa fare e cosa non fare

Quindi, in definitiva, che cosa contiene un test? Esistono differenze tra i tipi di test? Diamo un'occhiata più da vicino ad alcuni aspetti specifici personalizzati per i principali tipi di test.

Che cosa fa parte di un buon test unitario?

Un test unitario ideale ed efficace deve:

  • Concentrati su aspetti specifici.
  • Funzionano in modo indipendente.
  • Includono scenari su piccola scala.
  • Utilizza nomi descrittivi.
  • Segui il pattern AAA, se applicabile.
  • Garantire una copertura completa dei test.
Azioni consigliate ✅ Non ❌
Mantieni i test il più piccoli possibile. Testa un aspetto alla volta per ogni scenario di test. Scrivi test su unità di grandi dimensioni.
Mantieni sempre i test isolati e simula gli elementi di cui hai bisogno che non fanno parte dell'unità. Includi altri componenti o servizi.
Mantieni i test indipendenti. Fare affidamento su test precedenti o condividere i dati di test.
Copri scenari e percorsi diversi. Limitati al flusso ottimale o, al massimo, ai test negativi.
Utilizza titoli descrittivi per i test, in modo da capire immediatamente di cosa tratta il test. Test solo per nome della funzione, non essendo sufficientemente descrittivo: testBuildFoo() o testGetId().
Punta a una buona copertura del codice o a una gamma più ampia di casi di test, soprattutto in questa fase. Esegui test da ogni classe fino al livello del database (I/O).

Che cosa fa parte di un buon test di integrazione?

Un test di integrazione ideale condivide anche alcuni criteri con i test di unità. Tuttavia, ci sono un paio di punti aggiuntivi da considerare. Un buon test di integrazione deve:

  • Simula le interazioni tra i componenti.
  • Copri scenari reali e utilizza simulazioni o stub.
  • Prendi in considerazione il rendimento.
Azioni consigliate ✅ Non ❌
Prova i punti di integrazione: verifica che ogni unità funzioni correttamente quando è integrata con le altre. Testa ogni unità in modo isolato, è per questo che esistono i test delle unità.
Testa scenari reali: utilizza dati di test derivati da dati reali. Utilizzare dati di test ripetitivi generati automaticamente o altri dati che non riflettono casi d'uso reali.
Utilizza simulazioni e stub per le dipendenze esterne per mantenere il controllo del test completo. Creare dipendenze da servizi di terze parti, ad esempio richieste di rete a servizi esterni.
Utilizza una routine di pulizia prima e dopo ogni test. Non utilizzare misure di pulizia all'interno dei test, altrimenti potrebbero verificarsi errori di test o falsi positivi a causa della mancanza di un isolamento adeguato dei test.

Che cosa fa parte di un buon test end-to-end?

Un test end-to-end completo deve:

  • Replica le interazioni degli utenti.
  • Coprire scenari vitali.
  • Coprono più livelli.
  • Gestisci le operazioni asincrone.
  • Verifica i risultati.
  • Tieni conto del rendimento.
Azioni consigliate ✅ Non ❌
Utilizza le scorciatoie basate su API. Ulteriori informazioni. Utilizza le interazioni con l'interfaccia utente per ogni passaggio, incluso il hook beforeEach.
Utilizza una routine di pulizia prima di ogni test. Presta ancora più attenzione all'isolamento dei test rispetto ai test di unità e di integrazione, perché in questo caso il rischio di effetti collaterali è maggiore. Dimentichi di eseguire la pulizia dopo ogni test. Se non elimini lo stato, i dati o gli effetti collaterali rimanenti, questi influiranno su altri test eseguiti in un secondo momento.
Considera i test end-to-end come test di sistema. Ciò significa che devi testare l'intero stack dell'applicazione. Testa ogni unità in modo isolato, è per questo che esistono i test delle unità.
Utilizza un numero minimo di simulazioni all'interno del test o non utilizzale affatto. Valuta attentamente se vuoi simulare le dipendenze esterne. Fare eccessivo affidamento sui mock.
Prendi in considerazione le prestazioni e il carico di lavoro, ad esempio non sovraccaricando scenari di grandi dimensioni nello stesso test. Copri flussi di lavoro di grandi dimensioni senza utilizzare scorciatoie.