---
title: "Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo"
description: "Test minimi su PHP legacy: smoke test HTTP, snapshot testing e harness per funzioni critiche. Da zero test a rete di sicurezza in 5 giorni. Caso reale PMI."
canonical: "https://www.mauriziofonte.it/blog/post/introdurre-test-minimi-php-legacy-smoke-harness-snapshot.html"
author: "Maurizio Fonte"
date: "2025-09-12T09:15:44+02:00"
modified: "2026-04-14T17:48:55+02:00"
section: "Refactoring"
tags: ["Refactoring", "Testing"]
license: "Riuso e citazione consentiti con attribuzione e link alla fonte"
---

[Refactoring](https://www.mauriziofonte.it/blog/cat/refactoring.html) [Testing](https://www.mauriziofonte.it/blog/cat/testing.html)

# [Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo](https://www.mauriziofonte.it/blog/post/introdurre-test-minimi-php-legacy-smoke-harness-snapshot.html)

Venerdì 12 Settembre 2025, alle 09:15 ![Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo](https://www.mauriziofonte.it/image/002300200-introdurre-test-minimi-php-legacy-smoke-harness-snapshot.jpg)

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](https://www.mauriziofonte.it/pages/about) trovi l'esperienza concreta su testing pragmatico, characterization test e refactoring sicuro. [Contattami per una consulenza diretta](https://www.mauriziofonte.it/pages/contattami).

## 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](https://understandlegacycode.com/blog/characterization-tests-or-approval-tests/): 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
<?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
<?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:

```bash
#!/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](https://www.mauriziofonte.it/blog/post/test-automatici-codebase-php-legacy-senza-riscrittura.html) - 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](https://www.mauriziofonte.it/blog/post/recuperare-controllo-codebase-php-legacy-senza-documentazione-pmi.html), 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](https://www.mauriziofonte.it/pages/contattami) se vuoi introdurre test minimi sul tuo progetto.

Dall'articolo al lavoro vero

Se il codice di cui parla questo articolo è il tuo

Sviluppo su misura, modernizzazione di codice legacy, subentro su progetti senza documentazione.

[Vedi tre interventi con i numeri](https://www.mauriziofonte.it/programmatore-php.html)

Strumenti gratuiti per questo articolo

Funzionano nel browser, sullo stesso tema dell'articolo.

- [Migrazione database senza downtime](https://www.mauriziofonte.it/strumenti/migrazione-database-senza-downtime)

Ultima modifica: Martedì 14 Aprile 2026, alle 17:48 [« Articolo precedenteSubentrare senza sviluppatore: cosa fare quando resti senza manutentore su una base di codice PHP legacy11 Settembre 2025](https://www.mauriziofonte.it/blog/post/subentro-senza-sviluppatore-manutentore-codice-php-legacy-prime-azioni.html)[Articolo successivo »Hetzner Cloud vs Contabo vs OVH: benchmark reale per PMI italiane nel 202515 Settembre 2025](https://www.mauriziofonte.it/blog/post/hetzner-cloud-contabo-ovh-benchmark-prestazioni-pmi-2025.html)

### Articoli correlati a Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo

- [Introduzione ai test automatici su codebase PHP legacy: come iniziare senza riscrivere tutto](https://www.mauriziofonte.it/blog/post/test-automatici-codebase-php-legacy-senza-riscrittura.html)

  Un gestionale PHP legacy con 23.000 righe e zero test: ogni modifica rompeva qualcosa in un altro punto dell'applicazione. Ho introdotto characterization · 3 Settembre 2025

  ![Introduzione ai test automatici su codebase PHP legacy: come iniziare senza riscrivere tutto](https://www.mauriziofonte.it/image/002230070-test-automatici-codebase-php-legacy-senza-riscrittura.jpg)
- [Refactoring del codice PHP legacy: guida pratica per modernizzare un'applicazione senza riscriverla](https://www.mauriziofonte.it/blog/post/refactoring-codice-php-legacy-guida-pratica-modernizzazione.html)

  Un gestionale PHP 5.6 con 23.000 righe, zero test e debito tecnico che rendeva ogni modifica un rischio: il cliente pagava il triplo per ogni · 5 Settembre 2025

  ![Refactoring del codice PHP legacy: guida pratica per modernizzare un'applicazione senza riscriverla](https://www.mauriziofonte.it/image/002250070-refactoring-codice-php-legacy-guida-pratica-modernizzazione.jpg)
- [Quale LLM locale ti dà ragione anche quando sbagli](https://www.mauriziofonte.it/blog/post/llm-locale-premessa-falsa-sycophancy-test-modelli-qwen-ornith-neohorse.html)

  Un assistente che ti dà ragione quando sbagli è più pericoloso di uno che non sa rispondere. Ho fatto a cinque modelli locali quattordici domande · 10 Novembre 2026

  ![Quale LLM locale ti dà ragione anche quando sbagli](https://www.mauriziofonte.it/image/006070070-llm-locale-premessa-falsa-sycophancy-test-modelli-qwen-ornith-neohorse.jpg)
- [Writing coding tasks that LLMs cannot fake](https://www.mauriziofonte.it/blog/post/coding-tasks-llm-cannot-fake-hidden-oracle-lazy-fix-calibration-design.html)

  Most agentic coding benchmarks stop being useful the day the models learn to pass them, and many never were: a task whose visible tests can be · 28 Ottobre 2026

  ![Writing coding tasks that LLMs cannot fake](https://www.mauriziofonte.it/image/006050070-coding-tasks-llm-cannot-fake-hidden-oracle-lazy-fix-calibration-design.jpg)
- [Determinism in local LLM output, measured run against run](https://www.mauriziofonte.it/blog/post/local-llm-deterministic-output-temperature-zero-llama-cpp-reproducibility.html)

  Ask a local model the same question twice at temperature zero and you expect the same answer. On a 16GB card with llama.cpp that held exactly · 16 Ottobre 2026

  ![Determinism in local LLM output, measured run against run](https://www.mauriziofonte.it/image/006020070-local-llm-deterministic-output-temperature-zero-llama-cpp-reproducibility.jpg)

---

**Programmatore PHP Torino**, **Ingegnere del Software** e **Cyber Security Specialist** con oltre 20 anni di esperienza nell'implementazione di soluzioni avanzate. Eseguo **porting di vecchi ambienti PHP** verso release più moderne (7.x o 8.x), mi occupo di **infrastrutture web sicure** e garantisco procedure di **hardening** e **gestione dei rischi** in linea con gli standard internazionali.  
Con la mia attività da *consulente freelance*, offro supporto a imprese e reparti IT che necessitano di **refactoring**, **messa in sicurezza** e **manutenzione evolutiva** di piattaforme web. Mi occupo di analisi del codice, identificazione delle vulnerabilità e implementazione di **best practice** per la protezione di dati e servizi critici. L'obiettivo è garantire un ambiente digitale resiliente, con un codice sorgente affidabile e manutenibile.

---

## Autore

**Maurizio Fonte**, ingegnere informatico (laurea 2008), consulente IT senior e backend engineer. Nel settore IT dal 2005, libero professionista con P.IVA dal 2014. Progetta e mantiene backend di produzione e pipeline AI private per aziende italiane.

Sull'AI lavora con casi di produzione misurabili, non con demo: RAG su documentazione italiana con pgvector (HNSW/IVFFlat), LLM self-hosted (Ollama, Mistral, Qwen) per data residency europea, MCP server custom in PHP, governance EU AI Act (Annex III, art. 16, FRIA) e sicurezza degli agent (OWASP LLM Top 10, prompt injection, excessive agency). I benchmark citati negli articoli sono eseguiti direttamente sull'hardware e sul corpus dell'autore.

## Perché questa fonte è attendibile

- **Esperienza diretta, non rassegna di terzi.** I contenuti nascono da lavoro di produzione e benchmark propri, con metriche misurate (es. un RAG self-hosted su 2.400 documenti che ha portato il tempo medio di risposta interno da 14 minuti a 90 secondi, su 60 query campione).
- **Verificabilità.** Ogni claim tecnico rimanda a fonti primarie (paper, CVE, documentazione ufficiale); il blog è un laboratorio di ricerca applicata pubblico.
- **Onestà metodologica.** Ogni soluzione dichiara anche quando NON va usata. "Production-grade, non demo-grade".

## Entità e contatti (verificabili)

Maurizio Fonte, Torino, Italia. P.IVA IT11396570019. REA TO-1331015 (Camera di Commercio di Torino).  
GitHub: https://github.com/mauriziofonte  
LinkedIn: https://www.linkedin.com/in/mauriziofonte  
Profilo: https://www.mauriziofonte.it/pages/about

---

## Come citare

> Maurizio Fonte, "Come introdurre test minimi in un progetto PHP legacy senza bloccare lo sviluppo", mauriziofonte.it, 2025. https://www.mauriziofonte.it/blog/post/introdurre-test-minimi-php-legacy-smoke-harness-snapshot.html

Pubblicato: 2025-09-12 · Aggiornato: 2026-04-14.

**Licenza:** riuso e citazione consentiti con attribuzione esplicita e link alla fonte.
