PHPUnit e i test delle classi con trait e metodi condivisi

by theArchitect
SHARE
PHPUnit e i test delle classi con trait e metodi condivisi
© Guida-HTML5.it

Introduzione

In molti progetti PHP moderni, i trait vengono usati per riutilizzare logica comune tra classi diverse senza ricorrere a ereditarietà forzata. È una soluzione molto pratica, ma introduce anche una sfida nei test: come verificare in modo pulito il comportamento di una classe che usa un trait?

Questo tutorial mostra un approccio concreto con PHPUnit per testare una classe che utilizza un trait con metodi condivisi. Vedremo come isolare il comportamento, come evitare test fragili e come scrivere test leggibili anche quando la logica è distribuita tra classe e trait.

L’argomento è utile perché i trait sono spesso usati per funzionalità trasversali come:

  • formattazione dati
  • logica di validazione
  • normalizzazione di input
  • gestione di timestamp o slug

Se non li testi bene, rischi di verificare solo indirettamente il comportamento o, peggio, di duplicare troppa logica nei test.

Codice completo

Prendiamo un esempio realistico: una classe Article usa un trait per generare uno slug a partire dal titolo. Il trait contiene una logica riutilizzabile, mentre la classe lo usa per impostare correttamente il proprio stato.

<?php
declare(strict_types=1);

namespace AppUtils;

trait SlugifyTrait
{
    public function slugify(string $text): string
    {
        // Converte in minuscolo
        $text = mb_strtolower($text, ´UTF-8´);

        // Sostituisce i caratteri non alfanumerici con trattini
        $text = preg_replace(´/[^a-z0-9]+/u´, ´-´, $text);

        // Rimuove trattini iniziali e finali
        $text = trim($text ?? ´´, ´-´);

        return $text !== ´´ ? $text : ´n-a´;
    }
}

namespace AppEntity;

use AppUtilsSlugifyTrait;

class Article
{
    use SlugifyTrait;

    private string $title;
    private string $slug;

    public function __construct(string $title)
    {
        $this-

Ora scriviamo i test PHPUnit. In questo caso vogliamo verificare due livelli:

  • il comportamento del trait in sé, tramite una classe concreta di supporto
  • il comportamento della classe Article, che usa il trait
<?php
declare(strict_types=1);

namespace Tests;

use AppEntityArticle;
use AppUtilsSlugifyTrait;
use PHPUnitFrameworkTestCase;

class SlugifyTraitTest extends TestCase
{
    public function testSlugifyConvertsTextToUrlFriendlySlug(): void
    {
        $helper = new class {
            use SlugifyTrait;
        };

        $this->assertSame(´phpunit-e-trait´, $helper->slugify(´PHPUnit e Trait´));
        $this->assertSame(´hello-world´, $helper->slugify(´Hello, World!´));
        $this->assertSame(´n-a´, $helper->slugify(´###´));
    }
}

class ArticleTest extends TestCase
{
    public function testArticleCreatesSlugFromTitle(): void
    {
        $article = new Article(´Introduzione a PHPUnit´);

        $this->assertSame(´Introduzione a PHPUnit´, $article->getTitle());
        $this->assertSame(´introduzione-a-phpunit´, $article->getSlug());
    }

    public function testRenameUpdatesTitleAndSlug(): void
    {
        $article = new Article(´Vecchio titolo´);
        $article->rename(´Nuovo Titolo!´);

        $this->assertSame(´Nuovo Titolo!´, $article->getTitle());
        $this->assertSame(´nuovo-titolo´, $article->getSlug());
    }
}

Spiegazione

La parte importante di questo approccio è capire cosa testare e in che modo.

1. Testare il trait direttamente con una classe anonima

In PHP non si istanzia un trait da solo. Per testarne il comportamento, una soluzione semplice e pulita è creare una classe anonima che lo usa. In questo modo puoi verificare la logica del trait senza dover costruire una classe di dominio artificiale solo per i test.

Nel test SlugifyTraitTest il trait viene usato da una classe anonima:

  • è veloce da scrivere
  • evita file di supporto inutili
  • mantiene il test focalizzato sulla logica del trait

2. Testare la classe che usa il trait

La classe Article non deve essere testata ripetendo la stessa logica del trait. Qui il focus cambia: vogliamo verificare che la classe utilizzi correttamente il trait nei punti giusti, ad esempio nel costruttore e nel metodo rename().

Questa distinzione è fondamentale per evitare test duplicati. Se il trait è già coperto da test dedicati, nei test della classe puoi concentrarti solo sul comportamento pubblico della classe stessa.

3. Evitare test fragili

Un errore comune è scrivere test che dipendono troppo dall’implementazione interna. Ad esempio, non serve controllare come il trait usa preg_replace() o mb_strtolower(); basta verificare l’output finale.

Questo rende i test più robusti: se in futuro cambi l’implementazione interna del trait, i test continueranno a funzionare finché il comportamento pubblico resta coerente.

4. Il valore dei test sul comportamento pubblico

Nel caso di Article, il comportamento pubblico è semplice ma importante: titolo e slug devono essere coerenti. Se il titolo cambia, anche lo slug deve aggiornarsi. È proprio questo il tipo di contratto che i test devono proteggere.

Best practice

  • Testa il trait attraverso una classe concreta o anonima: è il modo più semplice per verificarne il comportamento senza forzare l’architettura.
  • Non duplicare i test: se il trait è già testato, nei test della classe verifica solo l’integrazione funzionale del trait con la classe.
  • Preferisci il comportamento pubblico: evita di testare dettagli interni come l’ordine delle chiamate o variabili temporanee.
  • Tieni i trait piccoli e focalizzati: un trait troppo grande diventa difficile da comprendere e da testare.
  • Usa nomi chiari nei test: il nome del test deve descrivere il comportamento atteso, non il dettaglio tecnico.
  • Valuta se il trait è davvero la scelta giusta: se la logica cresce troppo, una classe dedicata o un servizio possono essere più facili da testare e mantenere.

Riepilogo

Testare classi che usano trait in PHPUnit è semplice se adotti una strategia chiara:

  • usa una classe anonima per testare direttamente il trait
  • verifica nella classe solo il comportamento pubblico che dipende dal trait
  • non controllare i dettagli interni dell’implementazione
  • mantieni i trait piccoli, riutilizzabili e ben definiti

Questo approccio ti permette di scrivere test più leggibili, meno fragili e più utili nel lungo periodo. È particolarmente efficace quando i trait vengono usati per logiche trasversali che devono restare affidabili in più classi del progetto.

Approfondisci con risorse ufficiali

  • PHPUnit Manual: documentazione ufficiale con esempi e best practice
  • PHP Manual - Traits: approfondimento sul funzionamento dei trait in PHP
  • PHPUnit Assertions: elenco completo delle asserzioni disponibili
  • PHPUnit Test Doubles: utile per capire quando usare mock, stub e classi di supporto

Se vuoi fare un passo in più, prova a estendere l’esempio aggiungendo test con casi limite: stringhe vuote, caratteri accentati, testo già in formato slug e input con spazi multipli. È un ottimo esercizio per imparare a progettare test davvero solidi.

SHARE