Sottotipi di codeunit (I parte) – Test e TestRunner

Salve lettore, in questo articolo voglio parlarti dei sottotipi di codeunit presenti in Business Central. Tralasciando il sottotipo Normal che è quello che conoscerai meglio (se sei uno sviluppatore) perché è quello che di default viene interpretato dal compilatore, in questa prima parte voglio parlarti di Test e TestRunner.

Una codeunit può essere di sottotipo Test o TestRunner se la proprietà è Subtype = Test o SubType = TestRunner.

Questi due sottotipi di codeunit sono utilizzati per testare la propria applicazione. I metodi di test vengono scritti come codice AL nella codeunit di test come una qualsiasi altra funzione AL. L’unica differenza che questa codeunit ha rispetto a una codeunit Normal è che la codeunit Normal, nel codice onRun, se qualcosa fallisce l’esecuzione viene terminata, mentre nella codeunit Test l’esecuzione continua. Dopodiché i risultati vengono inseriti in un log di registro e viene mostrato in un messaggio.

Una codeunit TestRunner, invece, è una codeunit che ha nel trigger onRun l’esecuzione di una o più codeunit di Test. Inoltre una codeunit TestRunner ha al suo interno i trigger OnBeforeTestRun e OnAfterTestRun per fare operazioni prima o dopo l’esecuzione del trigger OnRun. Ad esempio nel caso di OnBeforeTesRun potrebbe essere il caso di una inizializzazione di tabelle prima dell’esecuzione delle codeunit di test presenti nel trigger OnRun.

Quando è possibile eseguire codice AL di test?

La risposta è: sempre, tranne nel caso di ambiente cloud di produzione. Questo perché i test possono chiamare accidentalmente altre app presenti nell’ambiente cloud di produzione con la conseguenza di calo di performance e in alcuni casi anche perdita di dati. Invece è possibile utilizzare codeunit di Test e TestRunner per tutte le altre situazioni, cioè ambiente Sandbox nel cloud, ambiente di Produzione e Container Docker per quanto riguarda la versione OnPrem.

Come creare una codeunit di Test e dei metodi di Test?

Come detto, per creare una codeunit di Test è sufficiente impostare la proprietà Subtype = Test. Dopodiché hai la possibilità di creare tre tipi di metodi: test, handler e normal.

Un metodo è di tipo Test se ha l’attributo [Test] anteposto prima della dichiarazione di esso. In questo metodo è possibile inserire la logica di business che vuoi testare così come le transazioni necessarie.

Il metodo di tipo Handler ha l’attributo [Handler] anteposto prima della dichiarazione del metodo stesso. Con questo metodo puoi simulare l’inserimento dei dati che normalmente farebbe l’utente ad esempio tramite una pagina. Pertanto, simula l’interazione dell’utente, come messaggi di validazione, selezioni, inserimenti di valori.

Il metodo di tipo Normal ha l’attributo [Normal] anteposto prima della dichiarazione del metodo stesso. Con questo metodo non fai altro che invocare codice scritto con lo stesso design di qualsiasi altra codeunit. In pratica con questo metodo scrivi o ricicli una normale funzione già presente nella tua applicazione e che non vuoi controllare ad esempio con l’attributo Test perché già funzionante.

Gestione delle transazioni

Di default, ogni metodo di test viene eseguito in una transazione di database separata. Tuttavia, è possibile modificarne il comportamento agendo direttamente sull’attributo del metodo di test TransactionModel e sulla proprietà della codeunit TestRunner chiamata TestIsolation.

Attributo TransactionModel

Per creare test significativi è innanzitutto necessario comprendere l’esecuzione delle transazioni. In uno scenario tipico, un utente connesso a un client immette i dati in un campo di una pagina. Quindi inserisce un valore in un altro campo e poi in un altro campo ancora. Infine, salva e chiude la pagina. Ogni volta che un utente inserisce i dati in un campo, il codice AL può essere attivato e una nuova transazione viene avviata automaticamente. Il codice presente nel trigger viene eseguito in questa nuova transazione. I dati sul campo vengono, poi, inviati al server in cui vengono elaborati e aggiornati nel database. Al termine del codice AL nel trigger, il commit della transazione viene automaticamente eseguito nel database e la pagina viene aggiornata con i dati aggiornati.

È possibile, quindi, simulare questi comportamenti da codice tramite l’attributo TransactionModel utilizzabile solo nelle codeunit Test. In particolare, è possibile impostarlo a:

  • Significa che il codice non include dei metodi COMMIT. Ogni chiamata a metodi COMMIT verrà generato un errore. La maggior dei processi di business non chiama metodi COMMIT, essi si basano su COMMIT impliciti alla fine del trigger AL più esterno. Il test dovrebbe quindi essere:
    1. Il metodo di test avvia una transazione.
    2. Il metodo di test inizializza i dati nel database. Le modifiche al database vengono apportate alla transazione avviata dal metodo di test.
    3. I campi della pagina di prova vengono impostati o aggiornati. Le modifiche al database vengono apportate alla transazione avviata dal metodo di test.
    4. Il metodo di test legge i valori dei campi nella pagina di test o legge dal database per convalidate il test.
    5. AL termine del metodo di test, viene eseguito il rollback della transazione e il database viene riportato allo stato iniziale.
  • Impostare in questo modo se la transazione contiene delle COMMIT. In questo caso il flusso sarebbe:
    1. Il metodo di test avvia una transazione.
    2. Il metodo di test inizializza i dati nel database. Le modifiche al database vengono apportate alla transazione avviata dal metodo di test.
    3. I campi della pagina di prova vengono impostati o aggiornati. Le modifiche al database vengono apportate alla transazione avviata dal metodo di test.
    4. Quando viene chiamato il metodo COMMIT, viene eseguito il commit delle modifiche nel database.
    5. Il metodo di test legge i valori dei campi nella pagina di test o legge dal database per convalidate il test.
    6. Una volta completato il metodo di test, viene eseguito il commit delle modifiche nel database. Per riportare il database allo stato iniziale è necessario ripristinare manualmente le modifiche, eliminando, aggiornando o inserendo record oppure utilizzando la proprietà TestIsolation sulla codeunit TestRunner per eseguire il rollback delle modifiche.

Proprietà TestIsolation

Impostando questa proprietà è possibile agire a quale livello vogliamo che il database faccia rollback. Essa può assumere tre valori:

  • Disbled: non viene fatto rollback al cambiare dei dati sul database. I test non sono isolati, pertanto, qualsiasi cambiamento viene mantenuto sul database. E’ il valore di default se non specificato diversamente.
  • Codeunit: viene fatto il rollback di tutti i cambiamenti quando termina la codeunit di test.
  • Function: viene fatto il rollback di tutti i cambiamenti quando termina una funzione di test.

E’ consigliabile progettare i test in maniera indipendente l’uni dagli altri. Questo per evitare che un’interazione tra test infici in maniera negativa il risultato dello stesso test. Pertanto è sempre consigliabile utilizzare la proprietà TestIsolation soprattutto se i test avvengono simultaneamente sullo stesso database. Inoltre ricorda che se specifichi il rollback delle transazioni, viene eseguito rollback anche su quelle codeunit dove hai esplicitamente dichiarato il metodo COMMIT.

Esempio

Voglio mostrarti un piccolo esempio di come possono essere applicati questi concetti. Supponiamo di sviluppare due codeunit di test, una per verificare delle operazioni matematiche e l’altra per verificare come avviene l’inserimento di un’anagrafica articolo.

Codeunit MathFunctions
Codeunit Transaction on Item

Ora richiamiamo le due codeunit di Test con la codeunit TestRunner chiamata TestMyCode.

Codeunit TestMyCode

A questo punto pubblichiamo l’app e cerchiamo nella barra di ricerca la pagina AL Test Tool. 

Find AL Test Tool page
AL Test Tool Page

Adesso dobbiamo selezionare l’action “Get Test Codeunits” per selezionare le codeunit di test che vogliamo analizzare. Inoltre con l’action “Select TestRunner” selezioniamo la codeunit di TestRunner che vogliamo sia eseguita per le nostre codeunit di Test. Nel nostro caso non usiamo la nostra di TestRunner perché essa esegue in sequenza le due codeunit di test implementate, ma mostrerebbe solo un messaggio con l’errore. Utilizziamo, quindi, la codeunit “Test Runner – Isol. Codeunit” che salva i messaggi nella pagina.

Page SelectTests
Page Select TestRunner
Ready to test

Clicchiamo ora su una delle action:

  • Run Tests: si aprirà una finestra di dialogo se si vuole eseguire tutte le codeunit della lista oppure quella corrente (se posizionati in una codeunit eseguirà tutte le funzioni di essa).
  • Run Selected Tests: esegue la codeunit o la funzione corrente.

Prima di cliccare su una delle due action sopra sincerarsi di avere il flag Run su ciò che vogliamo eseguire.

Per comodità seleziono RunTests ed eseguo tutto.

Exectuion of test

Possiamo notare che sono stati incapsulati gli errori che ci aspettavamo. Sul nodo principale codeunit (quello in neretto per intenderci) vediamo l’ultimo errore, mentre sulle singole funzioni gli errori che sono emersi. Da notare anche che l’unica funzione che è andata a buon fine ha il risultato su “Sucess”. Infine puoi controllare anche i tempi di esecuzione. Un’informazione che sicuramente in uno scenario di test molto più complicato di questo può essere un aiuto.

Conclusioni

L’argomento test è spesso sottovalutato dallo sviluppatore, preso dalle miriadi di applicazioni che il capo progetto gli chiede, ma è una parte fondamentale dello sviluppo. Una buona applicazione non può essere sviluppata se dietro non c’è una fase di test accurata.

Se questo argomento ti ha incuriosito e vuoi approfondire qualcosa, fammelo sapere nei commenti e vedremo di discuterne insieme o approfondire con un altro post.

Lascia una risposta