Gli unit test, o test unitari, sono fondamentali nello sviluppo del software, in quanto garantiscono che i componenti di un’applicazione funzionino come previsto in modo isolato. Scrivendo test per unità di codice specifiche, è possibile identificare e correggere gli errori nelle prime fasi dello sviluppo, ottenendo un software più affidabile e stabile.

In una pipeline di continuous integration/continuous delivery (CI/CD), si possono eseguire questi test automaticamente dopo aver apportato modifiche alla base di codice. In questo modo si garantisce che il nuovo codice non introduca errori o interrompa le funzionalità esistenti.

Questo articolo sottolinea l’importanza dello unit testing nelle applicazioni Laravel, spiegando come scrivere gli unit test per un’applicazione Laravel distribuita con il servizio di Hosting di Applicazioni di Kinsta.

Introduzione a PHPUnit

PHPUnit è un framework di testing molto diffuso all’interno dell’ecosistema PHP, progettato per i test unitari. Dispone di una robusta suite di strumenti per la creazione e l’esecuzione di test che lo rendono una risorsa fondamentale per garantire l’affidabilità e la qualità della base di codice.

Laravel supporta i test con PHPUnit e fornisce dei comodi metodi di aiuto che permettono di testare la propria applicazione.

L’impostazione di PHPUnit in un progetto Laravel richiede una configurazione minima. Laravel fornisce un ambiente di test preconfigurato, che include un file phpunit.xml e una directory dedicata ai test per i propri file.

In alternativa, è possibile modificare il file phpunit.xml per definire opzioni personalizzate per un’esperienza di test su misura. È anche possibile creare un file di ambiente .env.testing nella cartella principale del progetto invece di utilizzare il file .env.

Layout predefinito dei test in Laravel

Laravel offre un layout di directory predefinito e strutturato. La cartella principale del progetto Laravel contiene una cartella test con le sottodirectory Feature e Unit. Questo layout rende semplice separare i diversi tipi di test e mantenere un ambiente di test pulito e organizzato.

Il file phpunit.xml in un progetto Laravel è cruciale per orchestrare il processo di testing, assicurare la coerenza delle esecuzioni dei test e permettere di personalizzare il comportamento di PHPUnit in base ai requisiti del progetto. Permette di definire le modalità di esecuzione dei test, tra cui la definizione delle suite di test, la specificazione dell’ambiente di test e l’impostazione delle connessioni al database.

Questo file specifica anche che la sessione, la cache e l’email devono essere impostate sul driver dell’array, assicurando che i dati della sessione, della cache e dell’email non persistano durante l’esecuzione dei test.

Esistono diversi tipi di test che si possono eseguire su un’applicazione Laravel:

  • Test unitari – si concentrano su singoli componenti del codice, come classi, metodi e funzioni. Questi test rimangono isolati dall’applicazione Laravel e verificano che determinate unità di codice funzionino come previsto. È importante notare che i test definiti nella directory tests/Unit non avviano l’applicazione Laravel, quindi non possono accedere al database o ad altri servizi offerti dal framework.
  • Test delle funzionalità – convalidano le funzionalità più ampie dell’applicazione. Questi test simulano le richieste e le risposte HTTP, permettendo di testare le rotte, i controller e l’integrazione dei vari componenti. I test delle funzionalità aiutano a garantire che le diverse parti dell’applicazione funzionino come previsto.
  • Test del browser – si spinge oltre automatizzando le interazioni con il browser. I test utilizzano Laravel Dusk, uno strumento di automazione e test del browser, per simulare le interazioni dell’utente, come la compilazione di moduli e il clic sui pulsanti. I test del browser sono fondamentali per convalidare il comportamento di un’applicazione e l’esperienza dell’utente nei browser del mondo reale.

Concetti di sviluppo guidato dai test

Il test-driven development (TDD), lo sviluppo guidato dai test, è un approccio allo sviluppo del software che enfatizza i test prima di implementare il codice. Questo approccio segue un processo noto come ciclo red-green-refactor.

Il ciclo di sviluppo guidato dai test che mostra il ciclo red-green-refactor.
Il ciclo di sviluppo guidato dai test che mostra il ciclo red-green-refactor.

Ecco una spiegazione di questo ciclo:

  • Fase red – si scrive un nuovo test per definire la funzionalità o un miglioramento di uno esistente prima di implementare il codice vero e proprio. Il test dovrebbe fallire (come indica il colore “rosso”) perché non c’è il codice corrispondente per farlo passare.
  • Fase green – si scrive il codice sufficiente a far passare il test fallito, trasformandolo da rosso a verde. Il codice non sarà ottimale, ma soddisfa i requisiti del caso di test corrispondente.
  • Fase refactor – si riformula il codice per migliorarne la leggibilità, la manutenibilità e le prestazioni senza modificarne il comportamento. In questa fase, si possono tranquillamente apportare modifiche al codice senza preoccuparsi di eventuali problemi di regressione, perché i casi di test esistenti li individuano.

Il TDD ha diversi vantaggi:

  • Individuazione precoce dei bug – il TDD aiuta a individuare i bug nelle prime fasi del processo di sviluppo, riducendo i costi e i tempi di risoluzione dei problemi più avanti nel ciclo di sviluppo.
  • Miglioramento del design – il TDD incoraggia un codice modulare e non vincolato per una migliore progettazione del software. Incoraggia a pensare all’interfaccia e alle interazioni dei componenti prima dell’implementazione.
  • Fiducia nella rifattorizzazione – si può rifattorizzare il codice con fiducia, sapendo che i test esistenti identificano rapidamente le regressioni introdotte durante la fase refactor.
  • Documentazione viva – i casi di test fungono da documentazione viva, fornendo esempi di come il codice dovrebbe comportarsi. Questa documentazione è sempre aggiornata perché i test che falliscono indicano problemi nel codice.

Nello sviluppo di Laravel, si applicano i principi TDD scrivendo test per componenti come controller, modelli e servizi prima di implementarli.

L’ambiente di testing di Laravel, incluso PHPUnit, fornisce metodi e asserzioni utili per facilitare il TDD, assicurando di creare test significativi e di seguire efficacemente il ciclo red-green-refactor.

Esempi di base di test unitari

Questa sezione spiega come scrivere un semplice test per verificare la funzionalità di un modello.

Prerequisiti

Per seguirci, avrete bisogno di quanto segue:

  • Soddisfare i prerequisiti elencati nella guida al blog di Laravel.
  • Un’applicazione Laravel. Questo tutorial utilizza l’applicazione creata nella guida linkata sopra. Potete leggerla e creare l’applicazione del blog, ma se avete bisogno solo del codice sorgente per implementare i test, seguite i passaggi qui sotto.
  • Xdebug è installato e configurato con la modalità di copertura abilitata.

Configurare il progetto

  1. Eseguite questo comando in una finestra di terminale per clonare il progetto.
    git clone https://github.com/VirtuaCreative/kinsta-laravel-blog.git
  2. Spostatevi nella cartella del progetto ed eseguite il comando composer install per installare le dipendenze del progetto.
  3. Rinominate il file env.example in .env.
  4. Eseguite il comando php artisan key:generate per generare una chiave app.

Creare ed eseguire i test

Per iniziare, assicuratevi di avere il codice del progetto sul computer. Il modello da testare è il modello Post definito nel file app/Http/Models/Post.php. Questo modello comprende diversi attributi compilabili, come title, description e image.

Il vostro compito consiste nel creare dei semplici test unitari per questo modello. Uno verifica che gli attributi siano impostati correttamente, mentre un altro esamina l’assegnazione di massa tentando di assegnare un attributo non riempibile.

  1. Eseguite il comando php artisan make:test PostModelFunctionalityTest --unit per creare un nuovo caso di test. L’opzione --unit specifica che si tratta di un test unitario e lo salva nella cartella tests/Unit .
  2. Aprite il file tests/Unit/PostModelFunctionalityTest.php e sostituite la funzione test_example con questo codice:
    public function test_attributes_are_set_correctly()
       {
           // create a new post instance with attributes
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // check if you set the attributes correctly
           $this->assertEquals('Sample Post Title', $post->title);
           $this->assertEquals('Sample Post Description', $post->description);
           $this->assertEquals('sample_image.jpg', $post->image);
       }
    
       public function test_non_fillable_attributes_are_not_set()
       {
           // Attempt to create a post with additional attributes (non-fillable)
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
               'author' => 'John Doe',
           ]);
    
           // check that the non-fillable attribute is not set on the post instance
           $this->assertArrayNotHasKey('author', $post->getAttributes());
       }

    Questo codice definisce due metodi di test.

    Il primo crea un’istanza Post con gli attributi specificati e, utilizzando il metodo di asserzione assertEquals, verifica che gli attributi title, description e image siano stati impostati correttamente.

    Il secondo metodo tenta di creare un’istanza Post con un attributo aggiuntivo non riempibile (author) e afferma che questo attributo non è impostato sull’istanza del modello utilizzando il metodo di asserzione assertArrayNotHasKey.

  3. Assicuratevi di aggiungere la seguente dichiarazione use nello stesso file:
    use App\Models\Post;
  4. Eseguite il comando php artisan config:clear per cancellare la cache di configurazione.
  5. Per eseguire questi test, eseguite il comando:
    php artisan test tests/Unit/PostModelFunctionalityTest.php

    Tutti i test dovrebbero essere superati e il terminale dovrebbe visualizzare i risultati e il tempo totale di esecuzione dei test.

Eseguire il debug dei test

Se i test falliscono, potete eseguire il debug seguendo questi passaggi:

  1. Esaminate il messaggio di errore nel terminale. Laravel fornisce messaggi di errore dettagliati che individuano il problema. Leggete attentamente il messaggio di errore per capire perché il test è fallito.
  2. Ispezionate i test e il codice che state testando per identificare le discrepanze.
  3. Assicuratevi di aver impostato correttamente i dati e le dipendenze necessarie per il test.
  4. Usate strumenti di debug come la funzione dd() di Laravel per ispezionare le variabili e i dati in punti specifici del codice di test.
  5. Una volta individuato il problema, apportate le modifiche necessarie e rieseguite i test finché non passano.

Test e database

Laravel offre un modo pratico per configurare un ambiente di test utilizzando un database SQLite in-memory, che è veloce e non conserva i dati tra le esecuzioni dei test. Per configurare l’ambiente del database di test e scrivere i test che interagiscono con il database, seguite questi passi:

  1. Aprite il file phpunit.xml e decommentate le seguenti righe di codice:
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
  2. Eseguite il comando php artisan make:test PostCreationTest --unit per creare un nuovo caso di test.
  3. Aprite il file tests/Unit/PostCreationTest.php e sostituite il metodo test_example con il codice seguente:
    public function testPostCreation()
       {
           // Create a new post and save it to the database
           $post = Post::create([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // Retrieve the post from the database and assert its existence
           $createdPost = Post::find($post->id);
           $this->assertNotNull($createdPost);
           $this->assertEquals('Sample Post Title', $createdPost->title);
       }
  4. Assicuratevi di aggiungere la seguente istruzione use:
    use App\Models\Post;

    Attualmente, la classe PostCreationTest estende la classe base PHPUnitFrameworkTestCase. La classe base è comunemente utilizzata per i test unitari quando si lavora direttamente con PHPUnit, al di fuori di Laravel, o quando si scrivono test per un componente non strettamente accoppiato a Laravel. Tuttavia, se avete bisogno di accedere al database, dovete modificare la classe PostCreationTest per estendere la classe TestsTestCase.

    Quest’ultima classe adatta la classe PHPUnitFrameworkTestCase alle applicazioni Laravel. Fornisce funzionalità aggiuntive e configurazioni specifiche di Laravel, come la semina del database e la configurazione dell’ambiente di prova.

  5. Assicuratevi di sostituire l’istruzione use PHPUnitFrameworkTestCase; con use TestsTestCase;. Ricordate che l’ambiente di test è stato impostato per utilizzare un database SQLite in-memory. Pertanto, dovete migrare il database prima di eseguire i test. Per farlo, usate il trait IlluminateFoundationTestingRefreshDatabase. Questo trait migra il database se lo schema non è aggiornato e resetta il database dopo ogni test per garantire che i dati del test precedente non interferiscano con i test successivi.
  6. Aggiungete la seguente istruzione use al file tests/Unit/PostCreationTest.php per incorporare questo trait nel codice:
    use Illuminate\Foundation\Testing\RefreshDatabase;
  7. Quindi, aggiungete la seguente riga di codice subito prima del metodo testPostCreation:
    use RefreshDatabase;
  8. Eseguite il comando php artisan config:clear per cancellare la cache di configurazione.
  9. Per eseguire questo test, eseguite il comando:
    php artisan test tests/Unit/PostCreationTest.php

    I test dovrebbero essere superati e il terminale dovrebbe visualizzare i risultati del test e il tempo totale di esecuzione.

Test di funzionalità

Mentre i test unitari controllano i singoli componenti dell’applicazione in modo isolato, i test funzionali controllano porzioni di codice più ampie, come ad esempio l’interazione tra diversi oggetti. I test delle funzionalità sono fondamentali per diversi motivi:

  1. Convalida end-to-end – conferma che l’intera funzionalità funziona senza problemi, comprese le interazioni tra i vari componenti come controller, modelli, viste e persino il database.
  2. Test end-to-end – coprono l’intero flusso dell’utente, dalla richiesta iniziale alla risposta finale, e possono scoprire problemi che i test unitari potrebbero ignorare. Questa capacità li rende preziosi per testare i percorsi degli utenti e gli scenari complessi.
  3. Garanzia dell’esperienza dell’utente – imita le interazioni dell’utente, aiutando a verificare che l’esperienza dell’utente sia coerente e che la funzione funzioni come previsto.
  4. Rilevamento delle regressioni – individua le regressioni e le modifiche al codice quando si introduce nuovo codice. Se una funzionalità esistente inizia a non funzionare in un test di funzionalità, significa che qualcosa si è rotto.

Ora create un test di funzionalità per PostController nel file app/Http/Controllers/PostController.php. Concentratrvi sul metodo store, sulla convalida dei dati in entrata e sulla creazione e archiviazione dei post nel database.

Il test simula la creazione di un nuovo post da parte di un utente attraverso un’interfaccia web, assicurandovi che il codice memorizzi il post nel database e reindirizzi l’utente alla pagina dell’indice dei post dopo la creazione. Per farlo, seguite questi passaggi:

  1. Eseguite il comando php artisan make:test PostControllerTest per creare un nuovo caso di test nella cartella tests/Features.
  2. Aprite il file tests/Feature/PostControllerTest.php e sostituite il metodo test_example con questo codice:
    use RefreshDatabase; // Refresh the database after each test
    
       public function test_create_post()
       {
           // Simulate a user creating a new post through the web interface
           $response = $this->post(route('posts.store'), [
               'title' => 'New Post Title',
               'description' => 'New Post Description',
               'image' => $this->create_test_image(),
           ]);
    
           // Assert that the post is successfully stored in the database
           $this->assertCount(1, Post::all());
    
           // Assert that the user is redirected to the Posts Index page after post creation
           $response->assertRedirect(route('posts.index'));
       }
    
       // Helper function to create a test image for the post
       private function create_test_image()
       {
           // Create a mock image file using Laravel's UploadedFile class
           $file = UploadedFile::fake()->image('test_image.jpg');
    
           // Return the path to the temporary image file
           return $file;
       }

    La funzione test_create_post simula la creazione di un nuovo post da parte di un utente che effettua una richiesta POST alla route posts.store con attributi specifici, tra cui una finta immagine generata dalla classe UploadedFile di Laravel.

    Il test verifica che il codice sia riuscito a memorizzare il post nel database controllando il conteggio di Post::all(). Verifica che il codice reindirizzi l’utente alla pagina dell’indice dei post dopo la creazione del post.

    Questo test assicura che la funzionalità di creazione dei post funzioni e che l’applicazione gestisca correttamente le interazioni con il database e i reindirizzamenti dopo l’invio dei post.

  3. Aggiungete le seguenti istruzioni use allo stesso file:
    use App\Models\Post;
    use Illuminate\Http\UploadedFile;
  4. Eseguite il comando php artisan config:clear per cancellare la cache di configurazione.
  5. Per eseguire questo test, eseguite questo comando:
    php artisan test tests/Feature/PostControllerTest.php

    Il test dovrebbe essere superato e il terminale dovrebbe mostrare i risultati del test e il tempo totale per eseguirlo.

Confermare la copertura dei test

La copertura dei test si riferisce alla quantità di codice che i test di unità, funzionalità o browser controllano, espressa in percentuale. Aiuta a identificare le aree non testate della base di codice e le aree non testate che potrebbero contenere bug.

Strumenti come la funzione di copertura del codice di PHPUnit e il report di copertura integrato di Laravel generano rapporti che mostrano quali parti della base di codice sono coperte dai test. Questo processo fornisce informazioni fondamentali sulla qualità dei test e vi aiuta a concentrarvi sulle aree che potrebbero richiedere ulteriori test.

Generare un report

  1. Eliminate i file tests/Feature/ExampleTest.php e tests/Unit/ExampleTest.php, poiché non sono stati modificati e potrebbero causare errori.
  2. Eseguite il comando php artisan test --coverage in una finestra di terminale. Dovreste ricevere un output come il seguente:
    Screenshot che mostra l'esecuzione del comando php artisan test --coverage. Mostra il numero totale di test superati e il tempo di esecuzione dei risultati. Inoltre elenca ogni componente della tua base di codice e la sua percentuale di copertura del codice
    Esecuzione del comando php artisan test --coverage.

    Il report sulla copertura del codice mostra i risultati dei test, il numero totale di test superati e il tempo di esecuzione dei risultati. Inoltre elenca ogni componente della base di codice e la sua percentuale di copertura del codice. Le percentuali rappresentano la percentuale di codice coperta dai test.

    Ad esempio, Models/Post ha una copertura del 100%, il che significa che tutti i metodi e le righe di codice del modello sono coperti. Il report sulla copertura del codice mostra anche la Copertura totale, ovvero la copertura complessiva del codice per l’intera base di codice. In questo caso, i test coprono solo il 65,3% del codice.

  3. Per specificare una soglia minima di copertura, eseguite il comando php artisan test --coverage --min=85. Questo comando imposta una soglia minima dell’85%. Dovreste ricevere il seguente output:
    Screenshot che mostra il report di copertura con una soglia minima dell'85%. L'ultima riga segnala che il test fallisce perché non soddisfa la soglia minima.
    Test con una soglia minima dell’85%.

    Le suite di test falliscono perché il codice non soddisfa la soglia minima dell’85%.

    Sebbene l’obiettivo sia quello di ottenere una copertura del codice più elevata, spesso pari al 100%, è più importante testare a fondo le parti critiche e complesse della propria applicazione.

Riepilogo

Adottando le best practice descritte in questo articolo, come la scrittura di test significativi e completi, il rispetto del ciclo red-green-refactor in TDD e l’utilizzo delle funzionalità di testing fornite da Laravel e PHPUnit, potrete creare applicazioni robuste e di alta qualità.

Inoltre, avete la possibilità di ospitare la vostra applicazione Laravel con l’infrastruttura rapida, sicura e affidabile di Kinsta. Potrete anche utilizzare l’API di Kinsta per avviare le distribuzioni all’interno delle vostre pipeline CI/CD attraverso piattaforme come GitHub Actions, CircleCI e altre ancora.

Jeremy Holcombe Kinsta

Content & Marketing Editor presso Kinsta, web developer di WordPress e content writer. Al di fuori di tutto ciò che riguarda WordPress, mi piacciono la spiaggia, il golf e il cinema. Ho anche i problemi di tutte le persone più alte della media ;).