Il Performance Testing è una tecnica di test non funzionale che osserva il comportamento di un sistema sotto un carico controllato per misurarne stabilità, tempi di risposta e throughput. Strumenti come Gatling permettono di validare requisiti critici, identificare colli di bottiglia infrastrutturali e capire come reagisce un sistema quando il traffico diventa serio.
L’Importanza Strategica del Performance Testing
Un sistema che passa tutti i test funzionali ma cede sotto carico, in produzione, è un sistema difettoso — anche se nessun bug compare nei report dei test funzionali. È da qui che parte tutto il ragionamento sui test di performance.
A differenza dei test funzionali, che verificano cosa fa il sistema (es. “L’invio di una richiesta restituisce lo stato 200?”), i performance test verificano come il sistema si comporta sotto pressione (es. “Con 500 utenti concorrenti, l’API risponde in meno di 300ms?”).
Le diverse sfumature del carico
Per coprire scenari diversi servono profili di carico diversi:
- Load Test: valuta se il sistema regge il traffico di produzione atteso mantenuto nel tempo.
- Stress Test: spinge il sistema oltre i limiti previsti per individuare il punto di rottura e osservare come degrada.
- Soak (Endurance) Test: un carico costante applicato per ore o giorni per far emergere memory leak o degradi prestazionali lenti.
- Spike Test: analizza la reazione a salti improvvisi e violenti di traffico, tipici di eventi promozionali o flash sales.
Validare gli SLA, dimensionare correttamente la capacità infrastrutturale e — il punto che in pratica fa la differenza — intercettare le regressioni prima che arrivino sull’utente finale: è qui che questi test ripagano l’investimento.atching regressions before they reach the end user: that’s where these tests pay back the investment.
Panorama dei Tool: una scelta filosofica
Le opzioni open source non mancano: dai veterani come Apache JMeter ai più recenti k6 (Go/JS) e Locust (Python), fino a utility minimali da linea di comando come ab (Apache Benchmark) utili per smoke test rapidi. Quando si arriva alla scelta, però, il confronto reale resta quasi sempre tra due filosofie opposte: l’approccio GUI-driven di JMeter e quello code-first di Gatling.
Tabella Comparativa: JMeter vs Gatling
| Caratteristica | Apache JMeter | Gatling |
| Approccio | Basato su interfaccia grafica (GUI) e XML | Code-first (Java, Kotlin, Scala) |
| Modello Esecutivo | Un thread dedicato per ogni utente virtuale | Asincrono / Event-driven (Akka, Netty) |
| Configurazione | File JMX complessi | DSL espressivo e leggibile |
| Scalabilità | Limitata dal numero di thread della JVM | Altissima (migliaia di utenti con pochi thread) |
| Integrazione CI/CD | Possibile, ma laboriosa | Nativa (Maven, SBT, Gradle) |
| Punti di Forza | Ampio ecosistema di plugin, maturità | Versioning su Git, code review, performance pure |
Nella nostra esperienza, JMeter è stato accantonato proprio per il modello GUI-driven: configurazioni e manutenzione sono diventate rapidamente un collo di bottiglia per il team, soprattutto quando si trattava di evolvere gli scenari di test in parallelo con il codice applicativo.
Focus su Gatling: Performance as Code
Gatling è uno strumento open source scritto interamente in Scala, gira su JVM e usa Netty come motore di rete: questa combinazione gli permette di simulare decine di migliaia di utenti virtuali anche da una sola macchina locale, perché i thread non sono legati uno-a-uno agli utenti virtuali come accade in JMeter. Per chi sviluppa in ambiente Java/Spring Boot, il fatto di poterlo trattare come una libreria — con tanto di plugin Maven ufficiale — rende molto rapida la configurazione e la manutenzione dei test.
Anatomia di un test in Gatling
Un test (o Simulation) in Gatling si organizza in quattro concetti chiave:
- Simulation: la classe entry-point che aggrega tutti gli elementi.
- Scenario:la sequenza logica di azioni che ogni utente virtuale esegue (es. login → ricerca → acquisto), tipicamente con pause randomiche tra le chiamate per simulare il comportamento reale.
- Injection: il profilo di carico, ovvero la curva degli utenti nel tempo — ad esempio una rampa lineare da 0 a 50 utenti al secondo in 30 secondi, seguita da una fase a carico costante.
- Assertions: i criteri di accettazione (es. tempo medio di risposta, percentuale massima di errori) che determinano il successo o il fallimento del build.
Feeder: dati dinamici per simulare traffico reale
Un dettaglio che fa la differenza nella qualità dei risultati è il modo in cui si iniettano i dati di input. Riutilizzare sempre lo stesso utente porta a falsi positivi (es. blocchi anti–brute force) e a cache lato database che falsano i tempi di risposta. La funzione feeder di Gatling risolve il problema: legge un file CSV o JSON (per esempio con 1.000 credenziali utente) e per ogni utente virtuale seleziona una riga, fornendo variabili dinamiche allo scenario. Gli scenari più complessi combinano più feeder — uno per le credenziali, uno per gli ID di entità di business — in modo che ogni utente virtuale operi su un contesto diverso e plausibile.
Caso Pratico
Un esempio pratico di utilizzo di Gatling è stato adottato per uno dei principali clienti di Bitrock. Il repository è organizzato per favorire il riuso del codice e la collaborazione tra chi scrive i test e chi mantiene l’applicazione. L’obiettivo concreto, in un progetto recente, era verificare che un applicativo basato su Spring Boot, Java e MySQL reggesse un carico medio modesto (intorno a 0,7 richieste al secondo, derivato da circa 15 milioni di operazioni di business all’anno) ma con picchi violenti — nell’ordine di 3.000–4.000 richieste al minuto durante eventi promozionali.
Metodologia e Struttura
La repo è strutturata come segue:
- simulations/: contiene classi specifiche per i profili di carico (Smoke, Load, Stress, Spike).
- scenarios/: catene di chiamate riusabili che modellano i flussi di business.
- resources/data/: file CSV o JSON (feeder) utilizzati per iniettare dati realistici (credenziali, ID prodotti) ed evitare che le cache dei database falsino i risultati.
Uno scenario realistico raramente è una singola chiamata: la creazione di un appuntamento, per esempio, richiede sei chiamate API in sequenza (login, ricerca slot disponibili, prenotazione, conferma e così via), con valori estratti dalla risposta di una chiamata e riutilizzati nelle successive tramite la session di Gatling. Modellare questi flussi end-to-end è ciò che rende il test rappresentativo del traffico reale.
Il flusso di esecuzione è completamente automatizzato lato build: il comando mvn gatling:test avvia la simulazione, raccoglie le metriche e genera un report HTML interattivo con statistiche dettagliate su percentili (p50, p95, p99) ed esito delle asserzioni.
Ambienti, analisi e individuazione dei colli di bottiglia
I test girano prima in ambiente di sviluppo per la validazione funzionale degli scenari e per confronti relativi prima/dopo una modifica (es. l’aggiunta di un indice su una tabella), e poi in UAT, dove la configurazione hardware è più vicina alla produzione e i numeri assoluti hanno valore. Quando serve simulare un carico geograficamente distribuito, Gatling si integra con piattaforme come BlazeMeter, che permette di lanciare la stessa simulazione da più region (Europa, Stati Uniti, Australia…) e leggere l’output segmentato per area di provenienza.
Il report HTML di Gatling è il punto di partenza, non l’arrivo. La vera analisi nasce dall’incrocio con i log applicativi, gli APM e dashboard di osservabilità (tipicamente Grafana e Kibana): è lì che si identificano le query SQL più lente, i pool di connessioni saturi o gli endpoint che superano i limiti di tempo. Nel progetto citato, ad esempio, l’API di ricerca slot è risultata la più problematica — concentrava gran parte dei timeout — e il database MySQL si è confermato come collo di bottiglia principale, sia per scelte di indicizzazione sia per la configurazione del pool di connessioni.
Versioning e gestione dei dati di test
Gli scenari sono versionati su repository Git, con un branch per ogni release dell’applicativo: questo garantisce che si sappia sempre quale versione dei test corrisponde a quale versione del sistema sotto test. I dati creati dalle simulazioni (appuntamenti, ordini, record vari) sono in genere trascurabili rispetto al volume del database UAT, ma per progetti più sensibili è prassi affiancare ai test un job esterno — tipicamente un job Kubernetes — che ripulisca i dati residui prima o dopo l’esecuzione.
Oggi i test vengono lanciati in occasioni mirate, di solito in sessioni concordate con gli altri team. L’integrazione con la pipeline CI/CD per esecuzioni notturne automatiche, con soglie sui tempi di risposta come criterio di pass/fail, è il passo successivo naturale: tecnicamente è già tutto pronto, è una scelta organizzativa.ds as pass/fail criteria, is the natural next step: technically, everything is already in place; it’s an organizational choice.
Conclusione
Adottare Gatling cambia il momento in cui ci si occupa di performance: non più una verifica una tantum prima del rilascio, ma un controllo che può girare a ogni push grazie all’integrazione in CI/CD. Le regressioni prestazionali si vedono subito, quando sono ancora economiche da correggere.
Il valore aggiunto del nostro lavoro in Bitrock non sta nello scrivere gli script — quella è la parte facile. Sta nel definire scenari che riflettano davvero il comportamento degli utenti reali (non quello che pensiamo facciano), nel risalire alla causa di colli di bottiglia non ovvi — thread pool saturi, query con piani di esecuzione che cambiano sotto carico, connessioni DB esaurite — e nel suggerire dove vale la pena investire nell’infrastruttura e dove invece basta una modifica di codice.
Autore: Federico Vidali, Software Engineer @ Bitrock