Handleiding - 12 min

Teststrategie

Een Archestack-app wordt in lagen getest, van de editor die een fout markeert terwijl je typt tot de pipeline die niet deployt zolang er een test faalt. De tests zelf horen bij de app: ze staan in zijn packages, reizen met hem mee naar elke werkruimte en draaien tegen een eigen sandboxdatabase, dus een test leest of wijzigt nooit iemands echte data.

Vijf lagen

LaagWat ze opvangtWanneer ze draait
De editorC#- en paginacode die niet compileert, een kolom of business entity die niet bestaat, een script module die met een verkeerd argument wordt aangeroepenTerwijl je typt
ProefrunsEen script of business event dat het verkeerde doet, uitgeprobeerd voordat het wordt opgeslagenTijdens het schrijven van een script
TestcasesGedrag: de regel die een business event afdwingt, wat een script module teruggeeft, wat een business entity leestBij elke wijziging, en vóór elke deployment
SchemacontrolesEen tabelwijziging die data verliest, of waardoor een getypt script niet meer zou compilerenVóór elke migratie en deployment
Geplande suitesAlles wat rond de app veranderd is: een andere package, een upgrade, data die een regel niet verwachtte's Nachts, of volgens een ander schema, in elke omgeving

De eerste twee vragen geen werk: de editor controleert tegen dezelfde compiler en hetzelfde datamodel waarmee de app draait, en de knop Test van een script module en de simulatie van een business event draaien als proefrun, waarbij schrijfacties via Db en Files gecontroleerd en vastgelegd maar nooit uitgevoerd worden, en mail en meldingen vastgelegd maar niet verstuurd. Al het andere dat een script zelf doet, is echt: HTTP-aanroepen, System.IO, en alles wat een NuGet-package doet, zoals een verbinding met een andere database. De rest van deze pagina gaat over de derde laag, de tests die je schrijft, en hoe de andere twee ze draaien.

Testcases: given, when, then

Een testcase is één bewering over de app, in drie delen: de rijen die klaargezet worden (given), het ene ding dat gedraaid wordt (when) en wat daarna waar moet zijn (then). Cases worden gegroepeerd in suites. Deze, uit de Service Planner-demo, bewijst dat het planbord een dag alleen als overboekt markeert wanneer de geplande uren van een technicus boven diens capaciteit uitkomen:

{
  "name": "Week load flags a day over capacity",
  "suite": "Service Planner",
  "isEnabled": true,
  "data": {
    "description": "A day is overbooked only when its planned hours go over the technician's capacity.",
    "given": [
      { "table": "service_technician", "rows": [
        { "name": "Ana Peeters", "daily_capacity_hours": 8 },
        { "name": "Bram Maes", "daily_capacity_hours": 8 }
      ] },
      { "table": "service_job", "rows": [
        { "title": "Heat pump install", "technician_id": 1, "planned_date": "2026-09-23", "estimated_hours": 5, "status": "Planned" },
        { "title": "Boiler service", "technician_id": 1, "planned_date": "2026-09-23", "estimated_hours": 4, "status": "Planned" },
        { "title": "Commissioning", "technician_id": 2, "planned_date": "2026-09-23", "estimated_hours": 8, "status": "Planned" }
      ] }
    ],
    "when": { "kind": "ScriptModule", "name": "GetPlannerWeek", "parameters": { "week_start": "2026-09-21" } },
    "then": [
      { "subject": "Result", "operator": "Succeeds" },
      { "subject": "Result", "operator": "Equals", "path": "technicians[0].days[2].overbooked", "expected": "true" },
      { "subject": "Result", "operator": "Equals", "path": "technicians[1].days[2].overbooked", "expected": "false" }
    ]
  }
}

Given

De rijen die de case nodig heeft, tabel per tabel, van boven naar onder geschreven in een lege sandbox. De rijen krijgen de id's 1, 2, 3 in de volgorde waarin ze geschreven worden, en daar wijst de foreign key van een latere fixture naar: hierboven is technician_id 1 Ana. Een tabel waar een andere fixture naar wijst, komt dus eerst, en een sleutel die de database genereert, laat je weg, net als de audit-kolommen en elke kolom waarvan de default volstaat. In de builder vult het kiezen van een tabel voorbeeldrijen in op basis van de namen en types van zijn kolommen, en een foreign key is een keuzelijst over de rijen die de fixtures erboven schrijven.

When

Precies één doel, genoemd bij zijn naam, nooit bij een id:

  • Script module: draait een module met de opgegeven parameters.
  • Business entity: leest een entity terug, eventueel gefilterd, om te controleren wat een pagina zou tonen.
  • Business event: vuurt een trigger af op een rij, op het genoemde moment (BeforeCreate, AfterUpdate enzovoort), zoals opslaan dat zou doen.
  • Script: vrije C# met alles wat een script module heeft, voor een case die meerdere dingen moet doen voordat de bewering gecontroleerd kan worden.

Then

Elke check is een onderwerp, een operator en meestal een verwachte waarde. De onderwerpen zijn Result (wat het doel teruggaf), Rows (rijen die daarna uit een tabel worden teruggelezen), Output (wat het doel logde), Error, Duration en Code. De operators zijn Equals, NotEquals, Contains, NotContains, GreaterThan, LessThan, IsNull, IsNotNull, Matches (een reguliere expressie), RowCount, Succeeds en Fails. Een pad zoals technicians[0].days[2].overbooked loopt een resultaat in, en een pad dat nergens heen leidt, laat de check falen en zegt waar het stopte.

Testen dat iets geweigerd wordt

Veel regels van een app zeggen nee: een business event weigert een opslag die iets zou breken. De case voor zo'n regel probeert op te slaan en controleert dat dat geweigerd werd, om de juiste reden, en dat er niets geschreven is:

// A zero estimate is refused.csx: the target, a save the business event must stop
await Db.CreateAsync("service_job", new { title = "Check radiator valves", estimated_hours = 0 });
return "saved";
"then": [
  { "subject": "Result", "operator": "Fails" },
  { "subject": "Error", "operator": "Contains", "expected": "Estimated hours must be more than zero" },
  { "subject": "Rows", "operator": "RowCount", "table": "service_job", "expected": "0" }
]

De tweede check is degene die ertoe doet. Fails alleen slaagt bij elke fout, ook bij een verkeerd gespelde kolom, en zo houdt een rode test het vaakst ongemerkt op met iets te testen. Pin de fout vast op zijn melding.

Codechecks

Een check die één onderwerp en één operator niet kunnen uitdrukken, is een codecheck: C# dat na het doel draait in dezelfde sandbox, met alles wat een script module heeft, ook de getypte sets op Db. Result, Output, Error, DurationMs en Failed bevatten de uitkomst van het doel, en Assert zegt wat het verwachtte en wat het vond wanneer een check faalt (Equal, NotEqual, True, False, Null, NotNull, Contains, Count en Fail). De check faalt wanneer hij een exception gooit, een Assert niet haalt of false teruggeeft.

// Planning a job marks it Planned.then.1.csx: a check one subject and one operator cannot say
var job = await Db.ServiceJob.SingleAsync(j => j.Title == "Boiler service");
Assert.Equal("Planned", job.Status);
Assert.NotNull(job.PlannedDate, "a planned job has a date");

var sameDay = await Db.ServiceJob
    .Where(j => j.TechnicianId == job.TechnicianId && j.PlannedDate == job.PlannedDate)
    .SumAsync(j => j.EstimatedHours);
Assert.True(sameDay <= 8, $"the day holds {sameDay} hours");

Gebruik er een wanneer een check meerdere rijen tegelijk nodig heeft, rekenwerk daarover, of een vergelijking tussen twee tabellen. In een repository is elke codecheck een eigen bestand, <Case>.then.<n>.csx, bewerkt met dezelfde C#-functies als elk script.

De sandbox

Een test komt nooit aan de data van de werkruimte. Een run leidt de datadatabase om naar een sandbox, een aparte database die op aanvraag gemaakt wordt in de vorm van de echte, en elke tabel erin wordt vóór elke case leeggemaakt, dus de enige rijen die een case ziet, zijn zijn eigen fixtures en wat zijn doel schrijft. De definities komen nog steeds uit de werkruimte, dus een test draait de business entities, business events en scripts die op het punt staan uitgerold te worden. Elke werkruimte heeft vier sandboxes, dus er kunnen vier runs tegelijk draaien; een vijfde wacht tot er een vrijkomt.

  • Mail en meldingen worden vastgelegd, niet verstuurd. Het resultaat van elke case toont wat zijn scripts gemaild zouden hebben en wie ze een melding gestuurd zouden hebben, dus een case kan dat ook controleren.
  • Bestanden blijven zoals ze zijn. Schrijf-, verplaats- en verwijderacties via Files bereiken de map van de workspace nooit: de run houdt ze voor zichzelf, zodat een net geschreven bestand teruglezen werkt.
  • Uitgaande aanroepen zijn echt. Al het andere dat een script zelf doet, gebeurt bij elke run: een HTTP-request, een SMTP-verbinding die het opent, System.IO, en alles wat een NuGet-package doet, zoals een verbinding met een andere database, schrijfacties inbegrepen. Houd aanroepen naar andere systemen buiten wat de tests draaien, of laat ze naar een testendpoint of een testdatabase wijzen.
  • De administratie is echt. Een run van een opgeslagen case legt zijn resultaat vast op de case en de suite en schrijft een entry in de Event Log. Een run van een niet-opgeslagen draft, via de Run-knop van de case-editor of vanuit VS Code, legt niets vast.

Waar tests staan

Een test hoort bij de package die hij test. Hij reist met die package mee bij een export, een import of via de Module Store, dus een module komt ergens nieuw aan met het bewijs dat hij nog werkt, en alles wat hij noemt (de script module, de business entity, het business event) noemt hij bij naam, dus hij wijst in elke werkruimte naar het juiste. In een repository zijn de tests van een package bestanden: tests/<suite>/suite.json, een JSON-bestand per case onder cases/, en een .csx naast een case voor een Script-doel of een codecheck. Ze worden gereviewd en gemerged zoals de code die ze testen.

Tests draaien

  • In de builder toont de pagina Test Suite de suites en hun cases, en draait ze een case, een suite of alles, met bij elke gefaalde check verwacht tegenover werkelijk. Run in de case-editor probeert de case uit voordat hij opgeslagen is.
  • In VS Code draait Run Tests een case, een suite, een package of elke package tegen het project, vier cases tegelijk, en toont elk resultaat in de boomweergave en in de view Testing. Zie Tests in de VS Code-handleiding.
  • Volgens een schema draait een suite met een cron vanzelf, in welke omgeving hij ook geïnstalleerd is: 0 0 2 * * ? is elke nacht om twee uur. Elke run schrijft één entry in de Event Log met het detail van elke check, en het laatste resultaat staat op de suite.
  • In de pipeline draaien de tests van elke package tegen een wegwerp-Archestack die uit de repository gebouwd is, voordat er iets gedeployd wordt, en één fout stopt de deployment. Zie Deploymentstrategie.
  • Vanuit een AI-assistent geeft de MCP Server een verbonden model dezelfde testgids en de tools om een case te schrijven, te draaien en te verbeteren tot hij slaagt.

Een resultaat lezen

ResultaatBetekenis
PassedElke check hield stand.
FailedHet doel draaide en een check hield geen stand. Elke gefaalde check zegt wat hij verwachtte en wat hij vond.
ErrorDe case kwam nooit aan zijn checks toe: een fixture liet zich niet invoegen, een doel heeft een verkeerde naam, of het doel gooide een exception terwijl de case dat niet verwachtte. Lees eerst de fout.
SkippedDe case of zijn suite staat uit.

Wat je eerst test

  1. De business events die data bewaken. Elke regel die een opslag of een verwijdering weigert, krijgt een case die geweigerd wordt en een case die toegestaan wordt, zodat de regel niet ontbreekt en ook niet te streng is.
  2. Script modules die rekenen. Controleer bij een paar rijen de getallen die de module teruggeeft, en de randgevallen: een lege dag, een dag die precies vol is, een dag erover.
  3. De leesacties waar de app op steunt. Een business entity als doel met een filter, en een RowCount, bewijst dat een pagina de rijen toont die ze moet tonen en geen enkele die ze niet mag tonen.
  4. Flows over meerdere stappen. Een Script-doel dat doet wat een gebruiker zou doen, en codechecks op alles wat erop had moeten volgen.

Tests die nuttig blijven

  • Eén case, één bewering. Een case die tien dingen controleert, zegt niets over welke van de tien kapotging.
  • Noem de case naar de regel. "A zero estimate is refused" leest in de lijst met resultaten als een specificatie.
  • Controleer gedrag, niet de fixture. Wie de rijen telt die hij net heeft ingevoegd, test de lader van de fixtures.
  • Pin elke fout vast op zijn melding, zoals hierboven.
  • Houd fixtures klein. Alleen de rijen die de bewering nodig heeft, zonder gegenereerde sleutels.
  • Houd tests bij de code. Een wijziging en de test die haar bewijst, horen in dezelfde package, en in dezelfde pull request.

Waar dit naartoe gaat

Archestack in VS Code behandelt tests draaien vanuit de editor, Deploymentstrategie de pipeline die ze vóór elke deployment draait, en de referentie de business events en script modules waar de meeste tests over gaan.