Tests Genereren met AI: Slim of Lui?
AI kan unit tests en integration tests schrijven. Maar hoe bruikbaar zijn die tests echt? Een praktische kijk op AI-gegenereerde testcode.
Jean-Pierre Broeders
Freelance DevOps Engineer
Tests Genereren met AI: Slim of Lui?
Testcode schrijven is het stiefkindje van softwareontwikkeling. Iedereen weet dat het moet, niemand heeft er zin in. Precies daarom is het een van de eerste dingen waar AI code generation op losgelaten wordt. Maar levert dat ook bruikbare tests op?
Waar AI-gegenereerde tests goed werken
Bij simpele utility functies en pure functions schittert AI-testgeneratie. Geef een functie als input, en er rolt een reeks tests uit die edge cases afdekt waar een mens niet meteen aan denkt.
Neem deze simpele validator:
public static bool IsValidIban(string iban)
{
if (string.IsNullOrWhiteSpace(iban)) return false;
iban = iban.Replace(" ", "").ToUpper();
if (iban.Length < 15 || iban.Length > 34) return false;
var rearranged = iban[4..] + iban[..4];
var numericIban = string.Concat(rearranged.Select(c =>
char.IsLetter(c) ? (c - 'A' + 10).ToString() : c.ToString()));
return BigInteger.Parse(numericIban) % 97 == 1;
}
Wat AI hier genereert is verrassend compleet: null input, lege strings, te kort, te lang, geldige Nederlandse IBANs, ongeldige checksums, strings met spaties. Dat zijn zo tien tests die anders handmatig uitgetikt moeten worden.
Het probleem met complexere scenario's
Zodra er dependencies in het spel komen — databases, externe APIs, message queues — wordt het een ander verhaal. AI genereert dan tests die compileren en groen zijn, maar eigenlijk niks testen.
Een typisch voorbeeld:
[Fact]
public async Task CreateOrder_ShouldReturnSuccess()
{
var mockRepo = new Mock<IOrderRepository>();
mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
.ReturnsAsync(true);
var service = new OrderService(mockRepo.Object);
var result = await service.CreateOrderAsync(new OrderRequest
{
ProductId = 1,
Quantity = 5
});
Assert.True(result.Success);
}
Ziet er prima uit toch? Maar deze test verifieert alleen dat als de mock true teruggeeft, de service ook true teruggeeft. De daadwerkelijke business logic — voorraadcheck, prijsberekening, kortingsregels — wordt volledig overgeslagen. De mock is zo ingesteld dat alles altijd slaagt.
Dit soort tests geven een vals gevoel van veiligheid. Coverage gaat omhoog, maar bugs worden er niet mee gevonden.
Een betere aanpak: AI als startpunt
Wat wel werkt is AI gebruiken als startpunt en dan handmatig aanscherpen. Genereer de boilerplate — de test class setup, de arrange-act-assert structuur, de standaard happy path. Voeg daarna zelf de edge cases toe die domeinkennis vereisen.
Voor integration tests is een hybride aanpak effectief:
public class OrderIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public OrderIntegrationTests(WebApplicationFactory<Program> factory)
{
_client = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Testcontainers voor echte database
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(opts =>
opts.UseNpgsql(_postgresContainer.GetConnectionString()));
});
}).CreateClient();
}
[Fact]
public async Task CreateOrder_WithInsufficientStock_Returns409()
{
// Seed: product met 2 items op voorraad
await SeedProduct(productId: 1, stock: 2);
var request = new { ProductId = 1, Quantity = 5 };
var response = await _client.PostAsJsonAsync("/api/orders", request);
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
}
}
De setup en structuur kan AI prima genereren. Die specifieke test — bestelling met te weinig voorraad moet een 409 geven — daar is domeinkennis voor nodig.
Mutation testing als kwaliteitscheck
Een handige truc om te controleren of gegenereerde tests daadwerkelijk iets testen: mutation testing. Tools als Stryker.NET passen kleine wijzigingen toe in de broncode (mutaties) en controleren of de tests dan falen.
dotnet stryker --project OrderService.csproj
Als een mutatie overleeft — de broncode is aangepast maar alle tests slagen nog steeds — dan test die testsuite dat stuk code niet echt. Bij AI-gegenereerde tests overleven vaak 40-60% van de mutaties. Bij handgeschreven tests is dat eerder 15-25%.
Dat verschil zegt genoeg.
Wanneer het wel en niet de moeite waard is
Goed inzetbaar:
- Pure functions en utilities
- DTO validatie
- Serialization/deserialization tests
- Boilerplate voor test setup
- Parameterized tests genereren voor bekende input/output combinaties
Beter handmatig:
- Business logic met complexe regels
- Race conditions en concurrency tests
- Security-gerelateerde tests
- Alles waar de test het gedrag moet specificeren in plaats van bevestigen
De valkuil van coverage-targets
Met AI is het verleidelijk makkelijk om 90%+ code coverage te halen. Maar coverage zonder kwaliteit is een metrics-spelletje. Een team dat 80% coverage heeft met doordachte tests is beter af dan een team met 95% coverage waarvan de helft gegenereerde happy-path tests zijn.
Het draait niet om het getal. Het draait om het vertrouwen dat die tests geven bij een deployment op vrijdagmiddag. En dat vertrouwen komt niet van een AI die braaf elke methode een keer aanroept.
Praktisch advies
Start met AI-generatie voor de saaie delen. De test fixtures, de builder patterns, de repetitieve assertions. Besteed de vrijgekomen tijd aan het schrijven van de tests die er echt toe doen — de edge cases die uit productie-incidenten komen, de scenario's die alleen iemand met domeinkennis kan bedenken.
Dat is geen luiheid. Dat is slim omgaan met de tools die beschikbaar zijn.
