Iniziare a utilizzare i test di unità Apex
Obiettivi di apprendimento
Al completamento di questa unità, sarai in grado di:
- Descrivere i vantaggi principali dei test di unità Apex.
- Definire una classe con metodi di test.
- Eseguire tutti i metodi di test di una classe ispezionare gli errori.
- Creare ed eseguire una serie di classi di test.
Test di unità Apex
Il framework di testing Apex permette di scrivere ed eseguire test per le classi e i trigger Apex su Lightning Platform. I test di unità Apex garantiscono un'elevata qualità del codice Apex e permettono di soddisfare i requisiti per la distribuzione Apex.
I test sono fondamentali per il successo dello sviluppo a lungo termine e sono una componente cruciale del processo di sviluppo. Il framework di testing Apex semplifica il test del codice Apex. Il codice Apex può essere scritto solo in un ambiente sandbox o in un'organizzazione Developer, non in produzione. Il codice Apex può essere distribuito in un'organizzazione di produzione a partire da una sandbox. Inoltre, gli sviluppatori di app possono distribuire il codice Apex ai clienti delle rispettive organizzazioni Developer caricando i pacchetti su AppExchange per Lightning Platform. Oltre a essere fondamentali per garantire la qualità, i test di unità Apex costituiscono anche un requisito per la distribuzione e l'implementazione di Apex.
I vantaggi dei test di unità Apex sono i seguenti.
- Assicurare il corretto funzionamento delle classi e dei trigger Apex.
- Disporre di una suite di test di regressione che è possibile rieseguire tutte le volte che le classi e i trigger vengono aggiornati, al fine di garantire che i futuri aggiornamenti dell'app non compromettano le funzionalità esistenti.
- Soddisfare i requisiti di copertura del codice per la distribuzione di Apex in produzione o per la distribuzione di Apex ai clienti tramite pacchetti.
- Fornire app di alta qualità all'organizzazione di produzione, rendendone più produttivi gli utenti.
- Fornire app di alta qualità agli abbonati ai pacchetti, aumentando la fiducia dei clienti.
Requisiti di copertura del codice per la distribuzione
Prima di poter distribuire il codice o di inviarlo ad AppExchange in Lightning Platform, almeno il 75% del codice Apex deve essere stato sottoposto a test e tutti i test devono essere stati superati. Oltre a ciò, ciascun trigger deve avere una certa copertura. Anche se la copertura del codice è un requisito per la distribuzione, non scrivere test solo per soddisfarlo. Assicurati di effettuare i test sui casi d'uso più comuni dell'app, ivi compresi i casi di test positivi e negativi e l'elaborazione di record singoli e in blocco.
Sintassi dei metodi di test
I metodi di test vengono definiti utilizzando l'annotazione @isTest
e presentano la seguente sintassi:
@isTest static void testName() { // code_block }
L'annotazione @isTest
accetta più modificatori tra parentesi e separati da spazi vuoti. Tratteremo uno di questi parametri più avanti.
La visibilità di un metodo di test non ha importanza, quindi dichiarare un metodo di test come pubblico o privato non fa differenza, poiché il framework di testing è sempre in grado di accedere ai metodi di test. Per questo motivo, i modificatori di accesso sono omessi nella sintassi.
I metodi di test devono essere definiti in classi di test, che sono classi annotate tramite @isTest
. Questa classe di esempio mostra la definizione di una classe di test con un metodo di test.
@isTest private class MyTestClass { @isTest static void myTest() { // code_block } }
Le classi di test possono essere private o pubbliche. Se si utilizza una classe di test solo per i test di unità, è necessario dichiararla come privata. Le classi di test pubbliche sono normalmente utilizzate per le classi data factory di test, che verranno trattate più avanti.
Esempio di test di unità: test della classe TemperatureConverter
Nel semplice esempio riportato di seguito è definita una classe di test con tre metodi di test. Il metodo di classe che si sta sottoponendo al test prende come input una temperatura in gradi Fahrenheit. Converte quella temperatura in gradi Celsius e restituisce il risultato convertito. Aggiungiamo la classe personalizzata e la relativa classe di test.
- Nella Developer Console, fai clic su File | New (Nuovo) | Apex Class (Classe Apex) e inserisci
TemperatureConverter
come nome della classe, quindi fai clic su OK.
- Sostituisci il corpo predefinito della classe con il codice seguente.
public class TemperatureConverter { // Takes a Fahrenheit temperature and returns the Celsius equivalent. public static Decimal FahrenheitToCelsius(Decimal fh) { Decimal cs = (fh - 32) * 5/9; return cs.setScale(2); } }
- Premi Ctrl+S per salvare la classe.
- Ripeti i passaggi precedenti per creare la classe
TemperatureConverterTest
. Aggiungi il codice seguente a questa classe.@isTest private class TemperatureConverterTest { @isTest static void testWarmTemp() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(70); System.assertEquals(21.11,celsius); } @isTest static void testFreezingPoint() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(32); System.assertEquals(0,celsius); } @isTest static void testBoilingPoint() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212); System.assertEquals(100,celsius,'Boiling point temperature is not expected.'); } @isTest static void testNegativeTemp() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(-10); System.assertEquals(-23.33,celsius); } }
La classe di test TemperatureConverterTest
verifica che il metodo funzioni come previsto chiamandolo con diversi input per la temperatura in gradi Fahrenheit. Ciascun metodo di test verifica un tipo di input: una temperatura calda, una temperatura di congelamento, una temperatura di ebollizione e una temperatura negativa. Le verifiche vengono effettuate chiamando il metodo System.assertEquals()
, che utilizza due parametri: il primo è il valore atteso e il secondo è il valore effettivo. Esiste un'altra versione di questo metodo che accetta un terzo parametro, una stringa che descrive il confronto in corso, utilizzata in testBoilingPoint()
. Questa stringa opzionale viene registrata se l'asserzione non riesce.
Eseguiamo i metodi di questa classe.
- Nella Developer Console, fai clic su Test | New Run (Nuova esecuzione).
- In Test Classes (Classi di test), fai clic su TemperatureConverterTest.
- Per aggiungere all'esecuzione del test tutti i metodi di test della classe
TemperatureConverterTest
, fai clic su Add Selected (Aggiungi selezionate).
- Fai clic su Run (Esegui).
- Nella scheda Tests (Test) è possibile vedere lo stato dei test in fase di esecuzione. Espandi l'esecuzione del test ed espandi ancora fino a visualizzare l'elenco dei singoli test eseguiti. Hanno tutti un segno di spunta verde.
Dopo aver eseguito i test, la copertura del codice viene generata automaticamente per le classi Apex e i trigger dell'organizzazione. Puoi controllare la percentuale di copertura del codice nella scheda Tests (Test) della Developer Console. In questo esempio, la classe che è stata testata, la classe TemperatureConverter
, ha una copertura del 100%, come mostrato in questa immagine.
Anche se è vero che un solo metodo di test avrebbe portato alla copertura completa della classe TemperatureConverter
, è comunque importante effettuare test su diversi input per garantire la qualità del codice. Chiaramente non è possibile verificare ciascun punto dati, ma è possibile verificare punti dati comuni e diversi intervalli di input. Ad esempio, è possibile verificare il passaggio di numeri positivi e negativi, di valori limite e di valori di parametri non validi per verificare il comportamento negativo. I test per la classe TemperatureConverter
verificano i punti dati comuni, come la temperatura di ebollizione e le temperature negative.
La classe di test TemperatureConverterTest
non copre gli input non validi o le condizioni limite. Le condizioni limite si riferiscono ai valori minimi e massimi. In questo caso, il metodo di conversione della temperatura accetta un valore di tipo Decimal
, che può contenere numeri grandi, superiori ai valori di tipo Double
. Per quanto riguarda gli input non validi, non esiste una temperatura non valida, ma l'unico input non valido è l'input nullo. Come viene gestito questo valore dal metodo di conversione? In questo caso, quando il runtime Apex dereferenzia la variabile parametro per valutare la formula, genera una System.NullPointerException
. È possibile modificare il metodo FahrenheitToCelsius()
in modo che verifichi la presenza di un input non valido e, in tal caso, restituisca null, quindi aggiungere un test per verificare il comportamento dell'input non valido.
Fino a questo punto, tutti i test vengono superati perché la formula di conversione utilizzata nel metodo di classe è corretta. Che noia, però! Proviamo a simulare un errore per vedere cosa succede quando un'asserzione non riesce. Ad esempio, modifichiamo il test relativo alla temperatura di ebollizione e inseriamo un valore atteso falso per la temperatura di ebollizione in gradi Celsius (0 anziché 100). Questo comporta un errore nel metodo di test corrispondente.
- Modifica il metodo di test
testBoilingPoint()
come mostrato di seguito.@isTest static void testBoilingPoint() { Decimal celsius = TemperatureConverter.FahrenheitToCelsius(212); // Simulate failure System.assertEquals(0,celsius,'Boiling point temperature is not expected.'); }
- Per eseguire lo stesso test, fai clic sull'ultima esecuzione nella scheda Tests (Test), quindi fai clic su Test | Rerun (Esegui di nuovo). L'asserzione in
testBoilingPoint()
non riesce e genera un errore irreversibile (un'eccezioneAssertException
che non può essere intercettata).
- Controlla i risultati nella scheda Tests (Test) espandendo l'ultimo test eseguito. L'esecuzione del test riporta che un test su quattro non è riuscito. Per avere maggiori dettagli sull'errore, fai doppio clic sull'esecuzione del test. I risultati dettagliati sono visualizzati in una scheda separata, come mostrato in questa immagine.
[Alt text: Controllo dei risultati di un test non riuscito nella Developer Console]
- Per visualizzare il messaggio di errore del test non riuscito, fai doppio clic all'interno della colonna Errors (Errori) del test non riuscito. Vedrai il messaggio seguente. Il testo descrittivo accanto ad
Assertion Failed:
(Asserzione non riuscita:) è il testo che era stato fornito nell'istruzioneSystem.assertEquals()
.System.AssertException:Assertion Failed:Boiling point temperature is not expected.:Expected:0, Actual:100.00
(Asserzione non riuscita: temperatura del punto di ebollizione inattesa. Attesa: 0, Effettiva: 100,00)
I dati di questi metodi di test sono numeri e non record Salesforce. Nella prossima unità, scoprirai come effettuare test sui record Salesforce e come impostare i dati.
Aumentare la copertura del codice
Nello scrivere i test, cerca di raggiungere la massima copertura del codice possibile. Non limitarti a puntare al 75% di copertura, che è la copertura minima richiesta da Lightning Platform per le implementazioni e i pacchetti. Più sono i casi di test coperti dai tuoi test, maggiori sono le probabilità che il tuo codice sia solido. A volte, anche dopo aver scritto metodi di test per tutti i metodi di classe, la copertura del codice non raggiunge il 100%. Uno dei motivi più comuni è la mancata copertura di tutti i valori dei dati per l'esecuzione di codice condizionale. Ad esempio, i valori di alcuni dati tendono a essere ignorati quando il metodo di classe contiene istruzioni if che causano l'esecuzione di rami diversi in base al fatto che la condizione valutata sia soddisfatta o meno. Assicurati che i metodi di test tengano conto di questi diversi valori.
Questo esempio include il metodo di classe getTaskPriority()
, che contiene due istruzioni if
. Il compito principale di questo metodo è quello di restituire una stringa che esprime una priorità basata sullo stato di residenza del lead indicato. Il metodo dapprima convalida lo stato e poi restituisce null se lo stato non è valido. Se lo stato è uguale a CA (California), il metodo restituisce "High" (Alta). In caso contrario, restituisce "Normal" (Normale) per qualsiasi altro valore dello stato.
public class TaskUtil { public static String getTaskPriority(String leadState) { // Validate input if(String.isBlank(leadState) || leadState.length() > 2) { return null; } String taskPriority; if(leadState == 'CA') { taskPriority = 'High'; } else { taskPriority = 'Normal'; } return taskPriority; } }
Questa è la classe di test per il metodo getTaskPriority()
. Il metodo di test chiama semplicemente getTaskPriority()
con uno stato ("NY").
@isTest private class TaskUtilTest { @isTest static void testTaskPriority() { String pri = TaskUtil.getTaskPriority('NY'); System.assertEquals('Normal', pri); } }
Eseguiamo questa classe di test (TaskUtilTest
) nella Developer Console e verifichiamo la copertura del codice per la classe TaskUtil
corrispondente coperta da questo test. Al termine dell'esecuzione del test, la copertura del codice per TaskUtil
viene indicata al 75%. Aprendo questa classe nella Developer Console, si vedono sei righe blu (coperte) e due righe rosse (non coperte), come mostrato in questa immagine.
Il motivo per cui la riga 5 non è stata coperta è che la nostra classe di test non conteneva un test per inserire un parametro di stato non valido. Allo stesso modo, la riga 11 non è stata coperta perché il metodo di test non ha inserito "CA" come stato. Aggiungiamo altri due metodi di test per coprire questi scenari. Di seguito viene mostrata la classe di test completa dopo l'aggiunta dei metodi di test testTaskHighPriority()
e testTaskPriorityInvalid()
. Se esegui nuovamente questa classe di test utilizzando i comandi Run all (Esegui tutto) o New Run (Nuova esecuzione), la copertura del codice per TaskUtil
è ora al 100%.
@isTest private class TaskUtilTest { @isTest static void testTaskPriority() { String pri = TaskUtil.getTaskPriority('NY'); System.assertEquals('Normal', pri); } @isTest static void testTaskHighPriority() { String pri = TaskUtil.getTaskPriority('CA'); System.assertEquals('High', pri); } @isTest static void testTaskPriorityInvalid() { String pri = TaskUtil.getTaskPriority('Montana'); System.assertEquals(null, pri); } }
Creare ed eseguire una suite di test
Una suite di test è una raccolta di classi di test Apex che vengono eseguite insieme. Ad esempio, è possibile creare una suite di test da eseguire ogni volta che si prepara una distribuzione o che Salesforce rilascia una nuova versione. Puoi anche configurare una suite di test nella Developer Console per definire un insieme di classi di test da eseguire regolarmente insieme.
In questo momento hai due classi di test nell'organizzazione. Queste due classi non sono correlate, ma per il momento facciamo finta che lo siano. Supponiamo che vi siano situazioni in cui vuoi eseguire queste due classi di test ma non vuoi eseguire tutti i test presenti nell'organizzazione. Puoi creare una suite di test che contenga entrambe le classi ed eseguire i test della suite.
- Nella Developer Console, seleziona Test | New Suite (Nuova suite).
- Inserisci
TempConverterTaskUtilSuite
come nome della suite, quindi fai clic su OK.
- Seleziona TaskUtilTest, tieni premuto il tasto Ctrl, quindi seleziona TemperatureConverterTest.
- Per aggiungere alla suite le classi di test selezionate, fai clic su >.
- Fai clic su Save (Salva).
- Seleziona Test | New Suite Run (Esecuzione nuova suite).
- Seleziona TempConverterTaskUtilSuite, quindi fai clic su > per spostare
TempConverterTaskUtilSuite
nella colonna Selected Test Suites (Suite di test selezionate).
- Fai clic su Run Suites (Esegui suite).
- Nella scheda Tests (Test) è possibile monitorare lo stato dei test in fase di esecuzione. Espandi l'esecuzione del test ed espandi ancora fino a visualizzare l'elenco dei singoli test eseguiti. Come durante l'esecuzione di singoli metodi di test, è possibile fare doppio clic sui nomi dei metodi per visualizzare i risultati dettagliati dei test.
Creare i dati di test
I record Salesforce creati nei metodi di test non vengono salvati nel database. Al termine dell'esecuzione del test, sono soggetti a un rollback. Il rollback è utile per effettuare i test perché evita la necessità di ripulire i dati del test dopo la sua esecuzione.
Per impostazione predefinita, i test Apex non hanno accesso ai dati preesistenti dell'organizzazione, ad eccezione dell'accesso agli oggetti relativi alle impostazioni e ai metadati, ad esempio gli oggetti User (Utente) o Profile (Profilo). Imposta i dati per i test. La creazione di dati di test rende i test più solidi e previene errori dovuti alla mancanza o alla modifica dei dati nell'organizzazione. È possibile creare i dati di test direttamente nel metodo di test o utilizzando una classe di test di utilità, come vedremo più avanti.
Ulteriori informazioni
- Per eseguire test con Apex e verificare la funzionalità del tuo codice, puoi utilizzare l'estensione Apex Salesforce per Visual Studio Code.
- È possibile salvare fino a 6 MB di codice Apex in ciascuna organizzazione. Le classi di test annotate con
@isTest
non contano ai fini di questo limite.
- Anche se viene eseguito il rollback dei dati di test, non viene utilizzato un database separato per i test. Di conseguenza, per alcuni sObject che hanno campi con vincoli univoci, l'inserimento di record sObject duplicati genera un errore.
- I metodi di test non inviano email.
- I metodi di test non possono effettuare callout verso servizi esterni. Durante i test è possibile utilizzare callout simulati.
- Le ricerche SOSL eseguite in un test restituiscono risultati vuoti. Se si vogliono ottenere risultati prevedibili, è necessario utilizzare
Test.setFixedSearchResults()
per definire i record che verranno restituiti dalla ricerca.
Risorse
-
Apex Developer Guide: Testing Best Practices (Guida per gli sviluppatori Apex: Best practice per i test)
-
Apex Developer Guide: What Are Apex Unit Tests? (Guida per gli sviluppatori Apex: Che cosa sono i test di unità Apex?)
-
Apex Developer Guide: Isolation of Test Data from Organization Data in Unit Tests (Guida per gli sviluppatori Apex: Isolamento dei dati di test dai dati dell'organizzazione nei test di unità)
-
Guida di Salesforce: Verifica della copertura codice
Prepararsi per la sfida pratica
Per completare la sfida pratica per questa unità dovrai creare una nuova classe denominata VerifyDate
copiando il codice riportato di seguito:
public class VerifyDate { //method to handle potential checks against two dates public static Date CheckDates(Date date1, Date date2) { //if date2 is within the next 30 days of date1, use date2. Otherwise use the end of the month if(DateWithin30Days(date1,date2)) { return date2; } else { return SetEndOfMonthDate(date1); } } //method to check if date2 is within the next 30 days of date1 private static Boolean DateWithin30Days(Date date1, Date date2) { //check for date2 being in the past if( date2 < date1) { return false; } //check that date2 is within (>=) 30 days of date1 Date date30Days = date1.addDays(30); //create a date 30 days away from date1 if( date2 >= date30Days ) { return false; } else { return true; } } //method to return the end of the month of a given date private static Date SetEndOfMonthDate(Date date1) { Integer totalDays = Date.daysInMonth(date1.year(), date1.month()); Date lastDay = Date.newInstance(date1.year(), date1.month(), totalDays); return lastDay; } }