Salta al contenuto principale

03 - AI-Generated Test Cases: Usare LLM come Test Writer Automatico

AI-Generated Test Cases: Usare LLM come Test Writer Automatico Scrivere test e noioso, richiede tempo e viene procrastinato.

Cosa ne pensi di questo articolo?

03 - AI-Generated Test Cases: Usare LLM come Test Writer Automatico
AI TestingFoto:Daniil KomovPexels

In Breve

AI-Generated Test Cases: Usare LLM come Test Writer Automatico Scrivere test e noioso, richiede tempo e viene procrastinato.

AI-Generated Test Cases: Usare LLM come Test Writer Automatico

Scrivere test e noioso, richiede tempo e viene procrastinato. Quante volte hai visto un PR senza un solo test nuovo perche "l'ho aggiunto al backlog"? I LLM stanno cambiando questo scenario: GitHub Copilot, Claude e GPT-4 possono generare test cases di qualita sorprendente a partire da codice sorgente, user stories o specifiche API - riducendo il tempo di scrittura test del 40-60% e abbassando la barriera psicologica che porta a saltarli.

Ma non basta chiedere all'AI di "scrivere i test" e usarli ciecamente. Questa guida mostra un workflow strutturato per generare test di alta qualita, validarli e integrarli nel pipeline CI.

Cosa Imparerai

  • Strategie di prompting per generare test cases efficaci
  • Workflow GitHub Copilot per unit test da codice sorgente
  • Generazione di test da user stories e specifiche BDD
  • Come validare la qualita dei test generati dall'AI
  • Integrazione con mutation testing per verificare il valore reale
  • Limiti e rischi: quando i test AI non bastano

Il Problema con i Test Scritti dall'AI senza Verifica

Prima di entrare nel workflow, un avvertimento critico: i LLM generano test che passano facilmente ma non necessariamente testano il comportamento giusto. Il pattern piu comune e il "test fantasma" - un test che controlla che result !== null invece di verificare il valore reale.

// ESEMPIO DI TEST AI GENERATO SCARSO (da evitare)
// Copilot ha generato questo - sembra un test, ma non verifica nulla di utile

import { calculateDiscount } from './pricing';

describe('calculateDiscount', () => {
  it('should return a value', () => {
    const result = calculateDiscount(100, 'PROMO10');
    expect(result).toBeDefined();        // INUTILE: non verifica il valore corretto
    expect(result).not.toBeNull();       // INUTILE: solo dice "non e null"
    expect(typeof result).toBe('number'); // DEBOLE: non verifica che sia 90
  });
});

// ESEMPIO DI TEST AI GENERATO BUONO (con prompt corretto)
describe('calculateDiscount', () => {
  it('applica sconto PROMO10 del 10% sul prezzo base', () => {
    expect(calculateDiscount(100, 'PROMO10')).toBe(90);
  });

  it('restituisce il prezzo originale per codice non valido', () => {
    expect(calculateDiscount(100, 'INVALID')).toBe(100);
  });

  it('applica sconto massimo del 50% anche per codici piu generosi', () => {
    expect(calculateDiscount(100, 'SUPER80')).toBe(50); // cap al 50%
  });

  it('gestisce prezzi a zero senza errori', () => {
    expect(calculateDiscount(0, 'PROMO10')).toBe(0);
  });

  it('lancia errore per prezzi negativi', () => {
    expect(() => calculateDiscount(-10, 'PROMO10')).toThrow(RangeError);
  });
});

Prompting Efficace per GitHub Copilot

La qualita dei test generati dipende direttamente dalla qualita del prompt. Ecco le strategie piu efficaci:

// STRATEGIA 1: Commento contestuale nel file di test
// Il commento guida Copilot nel generare test completi

// src/utils/string-validator.ts
export function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

export function validatePassword(password: string): {
  valid: boolean;
  errors: string[];
} {
  const errors: string[] = [];
  if (password.length < 8) errors.push('Minimo 8 caratteri');
  if (!/[A-Z]/.test(password)) errors.push('Almeno una maiuscola');
  if (!/[0-9]/.test(password)) errors.push('Almeno un numero');
  if (!/[!@#$%]/.test(password)) errors.push('Almeno un carattere speciale');
  return { valid: errors.length === 0, errors };
}

// src/utils/string-validator.test.ts
// Test per validateEmail e validatePassword.
// Coprire: email valide, email non valide (mancano @, dominio, TLD),
// password valida, password troppo corta, password senza maiuscola,
// senza numero, senza speciale, stringa vuota, valori limite.
// Usare describe nested per raggruppare casi validi e invalidi.
// I test devono essere deterministici, no mocks necessari.

import { validateEmail, validatePassword } from './string-validator';

describe('validateEmail', () => {
  describe('email valide', () => {
    test.each([
      ['user@example.com'],
      ['user.name+tag@company.co.uk'],
      ['123@numbers.org'],
    ])('%s deve essere valida', (email) => {
      expect(validateEmail(email)).toBe(true);
    });
  });

  describe('email non valide', () => {
    test.each([
      ['not-an-email'],
      ['missing@'],
      ['@nodomain.com'],
      ['spaces in@email.com'],
      [''],
    ])('%s deve essere non valida', (email) => {
      expect(validateEmail(email)).toBe(false);
    });
  });
});

describe('validatePassword', () => {
  it('password valida supera tutti i controlli', () => {
    const result = validatePassword('SecureP@ss1');
    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  it('password troppo corta produce errore specifico', () => {
    const result = validatePassword('Ab1@');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Minimo 8 caratteri');
  });
});

Generazione da User Stories con Claude

Claude e particolarmente efficace nel trasformare user stories in casi di test strutturati, inclusi i casi limite che gli sviluppatori tendono a dimenticare.

"""
Prompt per Claude: Generazione test da User Story
"""

user_story = """
User Story: Come utente registrato, voglio aggiungere prodotti al carrello
in modo da poter procedere all'acquisto.

Criteri di accettazione:
- L'utente puo aggiungere un prodotto disponibile al carrello
- Il contatore nel header si aggiorna immediatamente
- Non si possono aggiungere prodotti esauriti
- La quantita massima per prodotto e 10
- Un prodotto gia nel carrello aumenta la quantita (non crea duplicato)
- I prodotti nel carrello persistono dopo refresh della pagina
"""

prompt = f"""Sei un QA engineer esperto in BDD e test automation.

Data la seguente user story e criteri di accettazione, genera:
1. Casi di test Playwright in TypeScript per i test E2E
2. Unit test per la funzione addToCart(productId, quantity) in Vitest
3. Test cases in formato Gherkin (BDD) per la documentazione

Per ogni test specifica:
- Scenario (nome descrittivo)
- Precondizioni
- Azioni
- Risultato atteso

Includi casi happy path, edge cases e casi di errore.

User Story:
{user_story}

Genera i test nel formato richiesto, con commenti che spiegano il
rationale di ogni caso di test."""

# Risposta Claude genera 15-20 test cases strutturati con Playwright + Vitest + Gherkin
# Esempio output Claude in Gherkin
Feature: Aggiunta prodotti al carrello

  Background:
    Given l'utente e autenticato
    And il catalogo prodotti e disponibile

  Scenario: Aggiunta prodotto disponibile
    When l'utente clicca "Aggiungi al carrello" sul prodotto "Laptop Pro X"
    Then il carrello contiene 1 unita di "Laptop Pro X"
    And il contatore nell'header mostra "1"

  Scenario: Prodotto gia nel carrello
    Given il carrello contiene 1 unita di "Laptop Pro X"
    When l'utente clicca "Aggiungi al carrello" sullo stesso prodotto
    Then il carrello contiene 2 unita di "Laptop Pro X"
    And non viene creato un record duplicato

  Scenario: Tentativo aggiunta prodotto esaurito
    Given il prodotto "Tablet Z" ha disponibilita 0
    Then il bottone "Aggiungi al carrello" per "Tablet Z" e disabilitato

  Scenario: Limite quantita massima
    Given il carrello contiene 10 unita di "Mouse Wireless"
    When l'utente tenta di aggiungere un'altra unita
    Then viene mostrato il messaggio "Quantita massima raggiunta (10)"
    And la quantita rimane 10

Workflow Completo: Da Codice a Test Validati

// Workflow in 5 step per test AI-generated di qualita

// STEP 1: Genera i test
// Prompt: "Genera test completi per questo servizio con casi happy path,
//          edge cases, casi di errore. Usa Vitest e TypeScript."
// [AI genera tests/payment.service.test.ts]

// STEP 2: Esegui i test generati
// npm run test -- tests/payment.service.test.ts
// ATTESO: tutti passano (se l'implementazione e corretta)

// STEP 3: Verifica mutation score
// npx stryker run -- solo sui file modificati
// TARGET: mutation score > 60% sui test AI-generated

// STEP 4: Review manuale
const REVIEW_CHECKLIST = `
[ ] I test coprono i criteri di accettazione?
[ ] I test verificano il comportamento (non l'implementazione)?
[ ] Ci sono edge cases mancanti? (null, undefined, stringhe vuote, valori limite)
[ ] I test sono deterministici? (no Date.now(), no Math.random() senza mock)
[ ] I test sono isolati? (no dipendenze tra test)
[ ] I nomi dei test descrivono il comportamento atteso?
[ ] I test falliscono correttamente se implementazione e errata?
`;

// STEP 5: Aggiungi test mancanti
// Usa mutation report per identificare mutanti non uccisi
// Aggiungi test mirati per i mutanti sopravvissuti

Validare i Test AI con Mutation Testing

Il modo piu affidabile per verificare che i test generati dall'AI abbiano valore reale e eseguire mutation testing su di essi:

// stryker.config.js - configurazione per analizzare test AI-generated
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
module.exports = {
  testRunner: 'vitest',
  coverageAnalysis: 'perTest',
  mutate: [
    // Muta solo i file per cui stiamo analizzando i test
    'src/services/payment.service.ts',
    'src/utils/pricing.ts'
  ],
  vitest: {
    configFile: 'vitest.config.ts'
  },
  thresholds: {
    high: 80,
    low: 60,
    break: 50    // Fallisce la build se mutation score scende sotto 50%
  },
  reporters: ['html', 'json', 'clear-text'],
  htmlReporter: {
    fileName: 'reports/mutation-report.html'
  }
};

Rischi e Limiti dell'AI Test Generation

  • Test che testano l'implementazione: l'AI tende a replicare la logica del codice nei test invece di verificare il comportamento. Revedi i test che sembrano "mirror" del codice sorgente
  • Mancanza di domain knowledge: l'AI non conosce le regole di business del tuo dominio specifico. I test critici di business logic devono essere scritti o revisti da un developer con contesto
  • False assurance: test generati che passano non significano test utili. Esegui sempre mutation testing prima di considerare la copertura completa
  • Hallucination nelle fixture: l'AI puo generare dati di test che sembrano validi ma non lo sono (date impossibili, numeri al di fuori del range, email formalmente valide ma semanticamente sbagliate)

Conclusioni

L'AI test generation e uno strumento potente quando usato correttamente: accelera la scrittura di boilerplate, genera edge cases che si dimenticano facilmente e abbassa la barriera psicologica al testing. Ma richiede un workflow di validazione - mutation testing e review manuale - per garantire che i test generati abbiano davvero valore.

La regola pratica: usa l'AI per generare l'80% del boilerplate e dei casi ovvi; scrivi tu il 20% che richiede domain knowledge.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti