Door AI gegenereerde testgevallen: LLM gebruiken als automatische testschrijver
Het schrijven van toetsen is saai, tijdrovend en uitstellend. Hoe vaak heb je een PR gezien? Waarom heb ik zonder een enkele nieuwe test "die aan de backlog toegevoegd"? LLM's brengen hier verandering in scenario: GitHub Copilot, Claude en GPT-4 kunnen testcases van verrassende kwaliteit genereren beginnend bij de broncode, gebruikersverhalen of API-specificaties, waardoor de schrijftijd wordt verkort tests met 40-60% te verlagen en de psychologische barrière te verlagen die ertoe leidt dat ze worden overgeslagen.
Maar het is niet genoeg om AI te vragen ‘de tests te schrijven’ en deze blindelings te gebruiken. Deze gids laat zien een gestructureerde workflow om tests van hoge kwaliteit te genereren, te valideren en te integreren in de CI-pijplijn.
Wat je gaat leren
- Strategieën uitlokken om effectieve testgevallen te genereren
- GitHub Copilot-workflow voor het testen van eenheden vanuit de broncode
- Testgeneratie op basis van gebruikersverhalen en BDD-specificaties
- Hoe de kwaliteit van door AI gegenereerde tests te valideren
- Integratie met mutatietesten om de echte waarde te verifiëren
- Grenzen en risico's: wanneer AI-tests niet voldoende zijn
Het probleem met tests die door AI zijn geschreven zonder verificatie
Voordat we ingaan op de workflow, een kritische waarschuwing: LLM's genereren tests die ze passeren
gemakkelijk, maar niet noodzakelijkerwijs, het juiste gedrag testen. Het meest voorkomende patroon is de
"fantoomtest" - een test die dat controleert result !== null in plaats van te controleren
de echte waarde.
// 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);
});
});
Effectieve prompting voor GitHub Copilot
De kwaliteit van de gegenereerde tests is rechtstreeks afhankelijk van de kwaliteit van de prompt. Hier zijn de strategieën effectiever:
// 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');
});
});
Generatie op basis van gebruikersverhalen met Claude
Claude is bijzonder effectief in het omzetten van gebruikersverhalen in gestructureerde testcases, inclusief randgevallen die ontwikkelaars vaak vergeten.
"""
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
Volledige workflow: van code tot gevalideerde tests
// 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
Valideer AI-tests met mutatietests
De meest betrouwbare manier om te verifiëren dat door AI gegenereerde tests echte waarde hebben, is voer mutatietests op hen uit:
// 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'
}
};
Risico's en grenzen van het genereren van AI-tests
- Tests die de implementatie testen: AI heeft de neiging logica te repliceren van code in tests in plaats van gedrag te verifiëren. Bekijk de tests waar ze op lijken "spiegel" van de broncode
- Gebrek aan domeinkennis: AI kent geen bedrijfsregels van uw specifieke domein. Kritieke bedrijfslogicatests moeten worden geschreven of beoordeeld van een ontwikkelaar met context
- Valse zekerheid: Gegenereerde tests die slagen, betekenen niet dat er bruikbare tests zijn. Voer altijd mutatietests uit voordat u een volledige dekking overweegt
- Hallucinatie in armaturen: AI kan testgegevens genereren die er zo uitzien geldig, maar dat zijn ze niet (onmogelijke datums, nummers buiten het bereik, e-mails formeel geldig maar semantisch onjuist)
Conclusies
Het genereren van AI-tests is bij correct gebruik een krachtig hulpmiddel: het versnelt het schrijven van de boilerplate, genereert randgevallen die gemakkelijk vergeten worden en verlaagt de barrière psychologie tot testen. Maar het vereist een validatieworkflow: het testen en beoordelen van mutaties handmatig – om ervoor te zorgen dat de gegenereerde tests echt waarde hebben.
De vuistregel: gebruikt AI om 80% van de standaardteksten en voor de hand liggende gevallen te genereren; Jij schrijft de 20% waarvoor domeinkennis vereist is.







