Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo

Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo

L'obiezione che sento da ogni titolare di PMI quando propongo di introdurre test su un'applicazione PHP legacy è: "non possiamo fermare lo sviluppo per scrivere test". È un'obiezione comprensibile ma sbagliata - perché il metodo che uso non ferma lo sviluppo, non richiede di riscrivere il codice esistente, e produce risultati misurabili in meno di una settimana. L'ho applicato su un gestionale PHP 7.2 di un cliente lombardo - distribuzione industriale, 18 operatori, zero test - che aveva una media di tre regressioni al mese in produzione. Ogni regressione significava un pomeriggio di debug urgente, operatori bloccati, e la fiducia del team che scendeva a ogni incidente. In cinque giorni ho introdotto 28 smoke test e 12 snapshot test - 40 test totali che hanno portato le regressioni in produzione a zero nel primo trimestre. In questo articolo ti racconto il metodo.

Stai cercando un Consulente Informatico esperto per introdurre test minimi sul tuo codebase PHP legacy? Nel mio profilo professionale trovi l'esperienza concreta su testing pragmatico, characterization test e refactoring sicuro. Contattami per una consulenza diretta.

Perché i test minimi funzionano meglio dei test completi su codice legacy?

La tentazione è scrivere una test suite completa che copra ogni caso d'uso. Su codice legacy è un errore: il codice non è strutturato per essere testabile (nessuna Dependency Injection, nessuna separazione di responsabilità, tutto accoppiato a tutto), e il tentativo di scrivere unit test classici richiede refactoring prima ancora di poter testare - il che è esattamente ciò che i test dovrebbero proteggere.

I test minimi - smoke test HTTP, snapshot test e harness per funzioni critiche - non richiedono che il codice sia strutturato in un modo particolare. Trattano l'applicazione come una scatola nera: invii un input, catturi l'output, e verifichi che non cambi tra un deployment e l'altro. L'approccio è documentato da Michael Feathers in Working Effectively with Legacy Code e viene chiamato characterization testing: non testi cosa il codice dovrebbe fare, ma cosa fa effettivamente.

Giorno 1-2: i smoke test HTTP - il sito funziona?

I smoke test sono i test più semplici possibili: verificano che gli endpoint critici dell'applicazione rispondano con il codice HTTP atteso e contengano elementi chiave nell'output. Non verificano la logica - verificano che il sistema sia vivo.

<?php
// tests/SmokeTest.php - PHPUnit
use PHPUnit\Framework\TestCase;

class SmokeTest extends TestCase
{
    private string $base = 'http://127.0.0.1';

    private function get(string $path): array
    {
        $ch = curl_init("{$this->base}{$path}");
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT => 10,
        ]);
        $body = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        return ['code' => $code, 'body' => $body];
    }

    /** @test */
    public function homepage_risponde(): void
    {
        $r = $this->get('/');
        $this->assertEquals(200, $r['code']);
    }

    /** @test */
    public function login_mostra_form(): void
    {
        $r = $this->get('/login.php');
        $this->assertEquals(200, $r['code']);
        $this->assertStringContainsString('password', $r['body']);
    }

    /** @test */
    public function pagina_inesistente_restituisce_404(): void
    {
        $r = $this->get('/pagina-che-non-esiste-xyz');
        $this->assertContains($r['code'], [404, 302]);
    }
}

Sul gestionale lombardo, i primi 28 smoke test coprivano: homepage, login, logout, ricerca prodotti, dettaglio prodotto, carrello, inserimento ordine, lista ordini, dettaglio ordine, stampa bolla, lista clienti, dettaglio cliente, report vendite, export CSV. Ciascun test verifica solo che l'endpoint risponda (HTTP 200 o redirect atteso) e che l'output contenga un elemento chiave (il titolo della pagina, un campo del form, una parola specifica). Ventotto test scritti in un giorno che catturano il 90% delle regressioni gravi - quelle in cui una modifica rompe una pagina intera.

Giorno 3-4: gli snapshot test - l'output è stabile?

Gli snapshot test (o golden master test) vanno un passo oltre i smoke test: catturano l'output completo di una funzione o di un endpoint e lo confrontano con una "fotografia" salvata. Se l'output cambia, il test fallisce - e lo sviluppatore deve decidere se il cambiamento è intenzionale (e aggiornare lo snapshot) o accidentale (e correggere il bug).

<?php
// tests/SnapshotTest.php
class SnapshotTest extends TestCase
{
    private function assertMatchesSnapshot(string $name, string $actual): void
    {
        $file = __DIR__ . "/snapshots/{$name}.txt";
        if (!file_exists($file)) {
            // Prima esecuzione: crea lo snapshot
            file_put_contents($file, $actual);
            $this->markTestIncomplete("Snapshot '{$name}' creato. Verificare e ri-eseguire.");
            return;
        }
        $this->assertEquals(file_get_contents($file), $actual,
            "Output cambiato rispetto allo snapshot '{$name}'");
    }

    /** @test */
    public function calcolo_totale_ordine_42(): void
    {
        // Chiama la funzione di calcolo con dati fissi
        require_once '/var/www/html/includes/calcoli.php';
        $result = calcola_totale_ordine(42);  // ordine di test
        $this->assertMatchesSnapshot('totale_ordine_42', json_encode($result));
    }

    /** @test */
    public function generazione_bolla_ordine_42(): void
    {
        $html = genera_bolla_html(42);  // bolla dell'ordine di test
        // Normalizza timestamp e dati variabili
        $html = preg_replace('/\d{2}\/\d{2}\/\d{4}/', 'DD/MM/YYYY', $html);
        $this->assertMatchesSnapshot('bolla_42', $html);
    }
}

La normalizzazione è fondamentale: timestamp, date, ID autoincrementanti e numeri di sessione cambiano a ogni esecuzione e devono essere sostituiti con placeholder fissi prima del confronto. Senza normalizzazione, lo snapshot test fallisce sempre e diventa inutile.

Sul gestionale lombardo, 12 snapshot test coprivano le funzioni di calcolo più critiche: calcolo totale ordine, calcolo IVA, calcolo sconti, generazione bolla, generazione fattura PDF, export listino CSV. Queste sono le funzionalità dove una regressione ha l'impatto maggiore - una fattura con IVA sbagliata è un problema legale, non solo tecnico.

Giorno 5: il pre-commit hook che blocca le regressioni

Quaranta test che nessuno esegue sono decorativi. Il pre-commit hook che installo assicura che ogni commit passi attraverso i test:

#!/bin/bash
# .git/hooks/pre-commit
echo "Test in corso..."
vendor/bin/phpunit --colors=never --no-coverage 2>&1
if [ $? -ne 0 ]; then
    echo "TEST FALLITI - commit bloccato. Correggi prima di committare."
    exit 1
fi
echo "Test OK."

Con questo hook, se uno sviluppatore fa una modifica che rompe uno degli endpoint testati o cambia l'output di una funzione di calcolo, il commit viene bloccato prima che il codice arrivi nel repository - e quindi prima che arrivi in produzione. Nel primo mese sul gestionale lombardo, il hook ha bloccato 7 commit - 7 regressioni che sarebbero finite in produzione senza i test.

Ho descritto il testing completo su codebase PHP legacy - inclusi characterization test avanzati, PHPUnit su progetti senza framework e CI gate - in un articolo dedicato. E per il contesto più ampio di recupero del controllo su un codebase PHP legacy senza documentazione, dove i test minimi sono il secondo passo dopo l'implementazione di Git.

I test minimi non sono una soluzione perfetta - non coprono tutto, non testano la logica interna, e non sostituiscono una test suite completa. Ma sono la soluzione pratica che funziona su codice legacy quando la scelta è tra "40 test in 5 giorni" e "nessun test per altri 12 mesi perché non abbiamo tempo". Se il tuo codebase PHP ha zero test e le regressioni sono una costante, cinque giorni di lavoro possono cambiare la qualità della vita del tuo team. Contattami se vuoi introdurre test minimi sul tuo progetto.

Ultima modifica: