Je kent die service. Vierhonderd regels business logic, nul tests, en een backlog-item dat al sinds Q3 zegt “unit tests toevoegen.” Niemand pakt het op, want tests achteraf schrijven is vervelend werk. Je weet al wat de code doet — waarom zou je een halve dag besteden aan het bewijzen ervan?

Tot iemand een method signature wijzigt en de hele orderstroom in productie omvalt. Dan denk je: die tests hadden dit gevangen.

Claude Code kan ze voor je schrijven. Niet perfecte tests. Maar een stevig startpunt waar je handmatig uren over had gedaan.


Stap 1: Laat Claude Code analyseren wat getest moet worden

Start Claude Code in je project en wijs het naar de class:

“Analyseer OrderService.cs en vertel me welke testcases ik nodig heb. Schrijf nog geen code — geef alleen de lijst.”

Claude Code leest het bestand, volgt de dependencies, en komt terug met zoiets:

  • PlaceOrder — happy path met geldige order
  • PlaceOrder — gooit exception bij null customer
  • PlaceOrder — gooit exception bij lege orderitems
  • PlaceOrder — past korting toe als totaal boven drempel komt
  • PlaceOrder — roept repository precies één keer aan
  • CalculateTotal — telt items correct op
  • CalculateTotal — handelt items met hoeveelheid nul af

Dit is de stap die de meeste mensen overslaan. Ze springen direct naar “schrijf tests.” Maar eerst een testplan hebben betekent dat je kunt beoordelen of de cases kloppen voordat er code gegenereerd wordt.


Stap 2: Genereer de eerste testclass

Nu vraag je Claude Code om de tests te schrijven:

“Maak een xUnit-testclass voor OrderService. Gebruik FluentAssertions voor assertions en NSubstitute voor mocking. Begin met de happy path en de validatie-cases.”

Claude Code genereert zoiets als dit:

public class OrderServiceTests
{
    private readonly IOrderRepository _repository;
    private readonly IDiscountCalculator _discountCalculator;
    private readonly OrderService _sut;

    public OrderServiceTests()
    {
        _repository = Substitute.For<IOrderRepository>();
        _discountCalculator = Substitute.For<IDiscountCalculator>();
        _sut = new OrderService(_repository, _discountCalculator);
    }

    [Fact]
    public async Task PlaceOrder_WithValidOrder_SavesOrderToRepository()
    {
        // Arrange
        var customer = new Customer { Id = 1, Name = "Acme Corp" };
        var items = new List<OrderItem>
        {
            new() { ProductId = 10, Quantity = 2, UnitPrice = 25.00m }
        };

        // Act
        await _sut.PlaceOrder(customer, items);

        // Assert
        await _repository.Received(1).Save(Arg.Is<Order>(o =>
            o.CustomerId == 1 && o.Items.Count == 1));
    }

    [Fact]
    public async Task PlaceOrder_WithNullCustomer_ThrowsArgumentException()
    {
        // Arrange
        var items = new List<OrderItem> { new() { ProductId = 1, Quantity = 1, UnitPrice = 10m } };

        // Act
        var act = () => _sut.PlaceOrder(null!, items);

        // Assert
        await act.Should().ThrowAsync<ArgumentException>()
            .WithMessage("*customer*");
    }
}

Let op de Arrange-Act-Assert structuur, de _sut-naamconventie, en de leesbare FluentAssertions-syntax. Claude Code pikt de patronen van je project op als je een CLAUDE.md hebt die je testconventies beschrijft.


Stap 3: Theory-tests voor meerdere scenario’s

Voor cases met variërende input houdt [Theory] met [InlineData] het overzichtelijk:

“Voeg een Theory-test toe voor CalculateTotal met verschillende combinaties van hoeveelheden en prijzen.”

[Theory]
[InlineData(1, 10.00, 10.00)]
[InlineData(3, 15.50, 46.50)]
[InlineData(0, 99.99, 0.00)]
public void CalculateTotal_WithVariousItems_ReturnsExpectedTotal(
    int quantity, decimal unitPrice, decimal expectedTotal)
{
    // Arrange
    var items = new List<OrderItem>
    {
        new() { Quantity = quantity, UnitPrice = unitPrice }
    };

    // Act
    var result = _sut.CalculateTotal(items);

    // Assert
    result.Should().Be(expectedTotal);
}

Hier bespaart Claude Code echt tijd. Vijf [InlineData]-variaties handmatig uitschrijven is saai. Het patroon in één zin beschrijven is snel.


Stap 4: NSubstitute voor dependencies

De mock-setup is waar het interessant wordt. Claude Code begrijpt constructor injection en zet de substitutes correct op:

[Fact]
public async Task PlaceOrder_WhenTotalExceedsThreshold_AppliesDiscount()
{
    // Arrange
    var customer = new Customer { Id = 1, Name = "Big Spender" };
    var items = new List<OrderItem>
    {
        new() { ProductId = 1, Quantity = 10, UnitPrice = 100.00m }
    };
    _discountCalculator.Calculate(Arg.Any<decimal>())
        .Returns(50.00m);

    // Act
    await _sut.PlaceOrder(customer, items);

    // Assert
    await _repository.Received(1).Save(Arg.Is<Order>(o =>
        o.DiscountApplied == 50.00m));
}

De Arg.Any<>() en .Returns()-syntax is NSubstitute op z’n best. Claude Code gebruikt de juiste patronen zonder dat je het mocking-framework hoeft uit te leggen.


Stap 5: Vraag naar ontbrekende edge cases

Dit is mijn favoriete truc. Nadat de eerste tests zijn geschreven:

“Bekijk de testclass en de broncode. Welke edge cases mis ik?”

Claude Code vindt typisch dingen als:

  • Wat gebeurt er als de repository een exception gooit?
  • Wat als de kortingscalculator een negatieve waarde teruggeeft?
  • Wat bij gelijktijdige aanroepen met dezelfde klant?
  • Wat als de orderitems-lijst null is (niet leeg)?

Sommige hiervan zijn oprecht nuttig. Andere zijn over-engineering. Jij bepaalt welke je houdt. Maar het identificeren van edge cases doet Claude Code goed — het is systematischer dan wij mensen vaak zijn.


Stap 6: Uitvoeren en itereren

Nu draai je de tests:

dotnet test

Een paar zullen falen. Misschien ging Claude Code ervan uit dat een methode Task teruggeeft terwijl het Task<Order> is. Misschien klopt een property-naam niet helemaal. Dat is normaal.

Vertel Claude Code wat er faalde, en het fixt de test. De iteratieloop is snel: draaien, fixen, draaien, fixen. Binnen een paar rondes heb je een groene suite.


Wanneer Claude Code slechte tests schrijft

Eerlijk zijn hierover. Claude Code produceert slechte tests op voorspelbare manieren:

Implementatiedetails testen. Het verifieert graag dat een methode met specifieke argumenten is aangeroepen. Dat levert fragiele tests op die breken zodra je de interne werking refactort, ook al is het gedrag niet veranderd. Als je Received(1) in elke test ziet staan, duw terug.

Te veel mocks. Als je service vijf dependencies heeft en elke test alle vijf substitutes opzet, dan zeggen de tests je iets — maar over het ontwerp, niet over de tests. Claude Code zal niet zeggen “deze class heeft te veel verantwoordelijkheden.” Dat is jouw inschattingsvermogen.

Triviale assertions. Testen dat een property die je net hebt gezet de waarde heeft die je eraan gaf. Dat is geen test, dat is een spiegel. Verwijder deze.

Copy-paste patronen. Claude Code genereert soms tien tests die voor 90% identiek zijn. Vraag het om de gedeelde setup te refactoren naar helper-methodes.

De oplossing is simpel: review de tests zoals je elke code zou reviewen. Merge geen AI-gegenereerde tests zonder ze gelezen te hebben.


Tips voor betere prompts

Een paar dingen die ik heb geleerd over het prompten voor tests:

  • “Test het gedrag, niet de implementatie” — zeg dit expliciet. Het vermindert mock-zware tests.
  • “Eén assertion per test” — dwingt gerichte tests af in plaats van testmethodes die zeven dingen controleren.
  • “Gebruik beschrijvende testnamen die het scenario uitleggen” — je bedankt jezelf als een test over zes maanden faalt in CI.
  • “Test private methodes niet direct” — Claude Code probeert soms reflection te gebruiken. Stop dat.
  • Beschrijf je conventies in CLAUDE.md — als je een naampatroon gebruikt zoals MethodNaam_Scenario_VerwachtResultaat, vertel het Claude Code één keer en het volgt het overal.

Begin vandaag

Pak een service zonder tests. Open Claude Code en zeg:

“Analyseer deze class en schrijf xUnit-tests met FluentAssertions en NSubstitute. Test gedrag, niet implementatie. Gebruik Arrange-Act-Assert.”

Review wat er terugkomt. Verwijder de slechte tests. Houd de goede. Draai dotnet test.

Je gaat van nul naar 80% coverage in een middag. De laatste 20% — de edge cases, de integratiescenario’s, de dingen die menselijk oordeel vereisen — dat blijft jouw werk. Maar de sleur van de eerste 80% schrijven hoeft niet meer.