Teststrategie, acceptatieherleidbaarheid en kwaliteitsgrenzen
23.1 Intentie
Dit hoofdstuk beschrijft hoe OefenHub technisch aantoonbaar betrouwbaar wordt gemaakt. De teststrategie sluit aan op de gekozen middel-zware modulaire monoliet: één deploybare applicatie, één database en meerdere moduleprojecten met eigen DbContext, eigen schema en publieke contracten.
De teststrategie heeft vier doelen:
- aantonen dat de vastgestelde eisen uit de Software Requirements Specification en acceptatiecriteria technisch zijn afgedekt;
- bewaken dat modulegrenzen, dependency-richting en database-eigenaarschap niet ongemerkt worden doorbroken;
- voorkomen dat kritieke workflows halve functionele toestanden opleveren;
- zorgen dat nieuwe technische oefenmodules veilig toegevoegd kunnen worden zonder de kernapplicatie te destabiliseren.
23.2 Bronnen en afbakening
De Software Requirements Specification en acceptatiecriteria blijven leidend voor functionele testdekking. Dit hoofdstuk voegt geen nieuwe functionele eisen toe, maar beschrijft hoe bestaande eisen, modulecontracten, technische ontwerpkeuzes en kwaliteitsgrenzen worden getest.
Belangrijke inputbronnen zijn:
- Software Requirements Specification: acceptatiecriteria
- Software Requirements Specification: requirement-index
- Software Requirements Specification: oefenmodule-eisenregister
- Database-informatie: introductie
- Architectuur: architectuurbaseline
- Technisch Ontwerp: architectuuroverzicht en solution-opbouw
- Technisch Ontwerp: applicatielagen, projectstructuur en dependency-richting
- Technisch Ontwerp: databaseontwerp, migraties, seeddata en constraints
- Technisch Ontwerp: background jobs, TickerQ en periodieke verwerking
- Technisch Ontwerp: logging, audit, securitylogging en technische foutafhandeling
- Technisch Ontwerp: security, infrastructuur, secrets en omgevingen
23.3 Testprincipes
De teststrategie volgt deze principes.
| Principe | Betekenis |
|---|---|
| Test dicht bij eigenaarschap | Een module test primair haar eigen domeinregels, services, DbContext, seeddata en publieke contracten. |
| Modulegrenzen zijn testbaar | Dependency-richting, internal implementaties en publieke Contracts worden niet alleen afgesproken, maar via architecture tests bewaakt. |
| Geen testdekking via UI alleen | Kritieke regels mogen niet uitsluitend via end-to-endtests worden gevalideerd. Domein- en integratietests moeten de kernregels rechtstreeks testen. |
| Cross-module workflows zijn expliciet | Workflows die meerdere modules raken krijgen eigen integratietests met aandacht voor transaction boundaries, foutscenario's en idempotentie. |
| Security is verspreid maar niet impliciet | Omdat er geen apart securityproject is, moeten pipeline, authorization, headers, logging en veilige foutafhandeling expliciet worden getest. |
| Testdata is beheerst | Testdata mag geen echte persoonsgegevens, tokens, wachtwoorden of productiedata bevatten. |
| Traceability is aantoonbaar | Elke centrale eis uit de Software Requirements Specification is testbaar, beleidsmatig geborgd of bewust gemarkeerd als handmatig/operationeel te controleren. |
| Nieuwe oefenmodules zijn contractgedreven | Concrete technische oefenmodules worden getest tegen het generieke modulecontract voordat zij in de applicatie worden geregistreerd. |
23.4 Testprojecten en solution-organisatie
Testprojecten staan in de solution onder een organisatorische folder tests. De fysieke structuur mag hierop aansluiten, maar de solutionfolder is leidend voor overzichtelijkheid.
Voorbeeldstructuur:
src/
OefenHub.Web/
OefenHub.SharedKernel/
OefenHub.Infrastructure/
OefenHub.Scheduling/
OefenHub.Identity/
OefenHub.Authorization/
OefenHub.Relationships/
OefenHub.Catalog/
OefenHub.Modules.Abstractions/
OefenHub.ExerciseModuleHost/
OefenHub.Practice/
OefenHub.Communication/
OefenHub.Support/
OefenHub.LiveMonitoring/
OefenHub.Admin/
OefenHub.Reporting/
OefenHub.Modules.Arithmetic.Addition/
tests/
OefenHub.Identity.Tests/
OefenHub.Authorization.Tests/
OefenHub.Relationships.Tests/
OefenHub.Catalog.Tests/
OefenHub.ExerciseModuleHost.Tests/
OefenHub.Practice.Tests/
OefenHub.Communication.Tests/
OefenHub.Support.Tests/
OefenHub.LiveMonitoring.Tests/
OefenHub.Admin.Tests/
OefenHub.Reporting.Tests/
OefenHub.Scheduling.Tests/
OefenHub.Modules.Arithmetic.Addition.Tests/
OefenHub.Web.Tests/
OefenHub.EndToEndTests/
OefenHub.ArchitectureTests/
OefenHub.IntegrationTests/
Niet elk testproject hoeft op dag één te bestaan. Een module krijgt een eigen testproject zodra er domeinlogica, DbContext-configuratie, publieke contracten, kritieke workflows of module-specifieke regels aanwezig zijn.
23.5 Testprojectstructuur binnen een module
Een testproject mag eenvoudig beginnen en stapsgewijs groeien. Wanneer een testproject groter wordt, wordt de volgende structuur gebruikt.
OefenHub.Practice.Tests/
Unit/
Integration/
Contracts/
Data/
Workflows/
TestData/
TestDoubles/
| Map | Doel |
|---|---|
Unit | Snelle tests voor pure domeinlogica, berekeningen, statusovergangen en validators. |
Integration | Tests met echte module-DbContext, EF Core-configuratie, schema, migrations of testdatabase. |
Contracts | Tests op publieke modulecontracten en contractgedrag dat andere modules mogen gebruiken. |
Data | Tests voor seeddata, enumwaarden, queryfilters, indexes en database mapping. |
Workflows | Tests voor module- of cross-module usecases die meerdere stappen bevatten. |
TestData | Builders, scenariofixtures en veilige voorbeelddata. |
TestDoubles | Fakes/stubs voor publieke contracten van andere modules. |
De mappen zijn organisatorisch. Namespaces mogen eenvoudiger blijven wanneer dat de leesbaarheid vergroot.
23.6 Testlagen
| Testlaag | Primair doel | Eigenaar | Wanneer verplicht |
|---|---|---|---|
| Unit tests | Domeinlogica snel en geïsoleerd testen. | Module-eigen testproject. | Bij berekeningen, statusregels, validatie en modulecontractlogica. |
| Module integration tests | Module met eigen DbContext, schema en EF-configuratie testen. | Module-eigen testproject. | Bij databasewrites, queries, migrations, seeddata en constraints. |
| Contract tests | Publieke module-ingangen vastleggen. | Module-eigen testproject en/of integratietests. | Bij Contracts die door andere modules worden gebruikt. |
| Cross-module integration tests | Samenwerking tussen meerdere modules testen. | OefenHub.IntegrationTests. | Bij workflows die meerdere modules raken. |
| Architecture tests | Dependency-richting en modulegrenzen afdwingen. | OefenHub.ArchitectureTests. | Altijd voor project- en namespace-afspraken. |
| Web/component tests | Blazor componenten, routing en viewmodelcompositie testen. | OefenHub.Web.Tests. | Bij complexe UI-state, formulieren, rolweergaven en componentlogica. |
| End-to-end tests | Kritieke gebruikerspaden via de applicatie testen. | OefenHub.IntegrationTests of apart E2E-project. | Voor smoke tests en hoofdflows. |
| Security/configuration tests | Pipeline, headers, cookies, authorization en veilige defaults controleren. | Web, Authorization, Integration of Architecture tests. | Bij securitybaseline en releasechecks. |
| Performance/load tests | Belangrijke query's, PDF, SignalR en dashboards beoordelen. | Apart scenario of pipeline stage. | Bij bekende performancegevoelige flows. |
| Manual/operational checks | Zaken controleren die niet goed automatisch te testen zijn. | Releaseproces/beheer. | Bijvoorbeeld backup/restore, TickerQ-dashboardafscherming en productieconfiguratie. |
23.7 Unit tests
Unit tests zijn bedoeld voor logica die zonder database, netwerk, Blazor-rendering, Keycloak, TickerQ of QuestPDF getest kan worden.
Voorbeelden:
| Module | Voorbeeld unit test |
|---|---|
Practice | statistieken voor gemiddelde tijd, mediaan, IQR en uitschieters berekenen. |
Relationships | conflicterende uitnodigingen detecteren. |
Support | gebruikersstatus Opgelost of Gesloten afleiden uit sluitregistratie en heropentermijn. |
Catalog | oefeningstatus en zichtbaarheid bepalen. |
Communication | ongelezenstatus van thread-events bepalen. |
Authorization | actieve rolcontext en prioriteitsvolgorde bepalen. |
| Concrete oefenmodule | antwoordcontrole, vraaggeneratie en configuratievalidatie. |
Voorbeeld van een unit-testbare service:
public sealed class ExerciseRunStatisticsCalculatorTests
{
[Fact]
public void CalculatesMedianForEvenNumberOfQuestions()
{
// arrange
var durations = new[] { 2, 4, 8, 10 };
// act
var result = ExerciseRunStatisticsCalculator.Calculate(durations);
// assert
result.MedianSeconds.Should().Be(6);
}
}
Unit tests mogen test doubles gebruiken voor publieke contracten van andere modules. Zij mogen geen module-interne entities van andere modules gebruiken.
Voor interface-gebaseerde mocks is Moq de primaire mockinglibrary in de V1.0-baseline. Gebruik Moq vooral voor randafhankelijkheden en publieke contracten die in een unit test geïsoleerd moeten worden. Eenvoudige expliciete fakes, builders of in-memory testimplementaties blijven toegestaan wanneer zij leesbaarder of stabieler zijn. Afwijken naar een andere mockinglibrary gebeurt alleen wanneer een concrete testbehoefte aantoonbaar niet passend met Moq kan worden opgelost of wanneer security-/dependencyreview dat vereist.
23.8 Module integration tests
Module integration tests controleren of de module technisch correct werkt met haar eigen DbContext en schema.
Te testen onderwerpen:
- EF Core mapping;
- verplichte velden en nullable gedrag;
- indexes en unique constraints;
- delete behavior en soft-delete gedrag;
- queryfilters;
- seeddata;
- migrations;
- JSON/base64-payloadopslag;
- soft links en snapshotvelden;
- concurrency en idempotentie.
Voorbeeld:
public sealed class PracticeDbContextTests
{
[Fact]
public async Task CompletedRunsAreReturnedByHistoryQuery()
{
await using var db = await PracticeDbContextFactory.CreateAsync();
await db.ExerciseRuns.AddAsync(ExerciseRunTestData.CompletedRun());
await db.ExerciseRuns.AddAsync(ExerciseRunTestData.IncompleteRun());
await db.SaveChangesAsync();
var rows = await new ExerciseRunHistoryReader(db)
.GetCompletedHistoryAsync(StudentContextTestData.ValidStudent());
rows.Should().ContainSingle();
}
}
De testdatabase moet per test of testklasse schoon worden opgebouwd. Tests mogen elkaar niet afhankelijk maken van volgorde.
23.9 Architecture tests
Architecture tests bewaken dat de modulaire monoliet niet langzaam verandert in een ongestructureerde monoliet.
23.9.1 Toolkeuze
OefenHub gebruikt ArchUnitNET als primaire testtooling voor architecture tests in OefenHub.ArchitectureTests.
De baseline bestaat uit:
| Onderdeel | Keuze |
|---|---|
| Architecture-testlibrary | TngTech.ArchUnitNET |
| Testproject | tests/OefenHub.ArchitectureTests |
| Testadapter | ArchUnitNET-adapter passend bij het gekozen .NET-testframework; bij xUnit is dat TngTech.ArchUnitNET.xUnit of de actuele xUnit v3-adapter. |
| Uitvoering | Via dotnet test, als onderdeel van pull-request- en CI-checks. |
| Analysebasis | Gecompileerde assemblies van de OefenHub-projecten. |
De packageversie wordt tijdens implementatie vastgepind in het centrale packagebeheer. Een package-update mag de architectuurregels niet inhoudelijk versoepelen.
NetArchTest is beoordeeld als mogelijke alternatiefklasse, maar is niet de baseline. Afwijken van ArchUnitNET mag alleen met een expliciet TO-besluit, omdat de regels rond modulegrenzen, dependency-richting en publieke contracten anders opnieuw moeten worden gevalideerd.
23.9.2 Te bewaken regels
Minimaal te bewaken regels:
| Regel | Testcontrole |
|---|---|
Moduleprojecten refereren OefenHub.Web niet. | Assembly-/dependencycontrole met ArchUnitNET. |
OefenHub.Web gebruikt geen EF Core DbContexts van modules. | Type- en namespacecontrole. |
OefenHub.Web gebruikt geen module-interne entities. | Type-, namespace- en public API-controle. |
| Data/entities van modules zijn niet publiek bruikbaar over modulegrenzen. | Public API/typecontrole, aangevuld met internal-controle. |
Publieke module-ingangen staan onder Contracts. | Namespace/typecontrole. |
Module-interne services, repositories en DbContexts blijven buiten Contracts. | Namespace/typecontrole. |
Concrete oefenmodules refereren geen Catalog, Practice of Web. | Assembly-/dependencycontrole. |
Concrete oefenmodules gebruiken primair OefenHub.Modules.Abstractions. | Assembly-/dependencycontrole. |
SharedKernel bevat geen module-specifieke businesslogica. | Namespace- en naamgevingscontrole, aangevuld met review. |
| Cross-module database-toegang loopt niet via andermans DbContext. | Type-, namespace- en dependencycontrole. |
Domeinmodules hebben geen dependency op OefenHub.Scheduling voor domeinlogica. | Assembly-/dependencycontrole; alleen publieke schedulingcontracten zijn toegestaan waar nodig. |
Readmodels blijven module-eigen onder Models/ReadModels. | Namespace/typecontrole. |
23.9.3 Structuur van het architecture-testproject
OefenHub.ArchitectureTests gebruikt een vaste opbouw zodat regels niet per ontwikkelaar verschillend worden geïnterpreteerd.
tests/
OefenHub.ArchitectureTests/
ArchitectureLoad.cs
ProjectReferenceRules.cs
WebBoundaryRules.cs
ModuleBoundaryRules.cs
ContractBoundaryRules.cs
ExerciseModuleRules.cs
SharedKernelRules.cs
ReadModelRules.cs
Richtlijnen:
ArchitectureLoad.cslaadt de assemblies één keer centraal.- Elke ruleclass test één afgebakend type architectuurregel.
- Testnamen beschrijven de verboden richting, niet alleen het technische mechanisme.
- Foutmeldingen moeten de geschonden modulegrens of dependencyrichting benoemen.
- Architecture tests mogen geen database, Keycloak, SignalR, TickerQ of webhost starten.
- Architecture tests vervangen geen code review; ze blokkeren bekende, automatisch controleerbare overtredingen.
23.9.4 Voorbeeldregel
Voorbeeld van een ArchUnitNET-regel voor de intentie dat moduleprojecten niet afhankelijk mogen zijn van OefenHub.Web:
using ArchUnitNET.Domain;
using ArchUnitNET.Loader;
using ArchUnitNET.Fluent;
using Xunit;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
public sealed class ProjectReferenceRules
{
private static readonly Architecture Architecture = new ArchLoader()
.LoadAssemblies(ArchitectureAssemblies.All)
.Build();
private static readonly IObjectProvider<IType> Web =
Types().That().ResideInAssembly("OefenHub.Web").As("OefenHub.Web");
private static readonly IObjectProvider<IType> Catalog =
Types().That().ResideInAssembly("OefenHub.Catalog").As("OefenHub.Catalog");
[Fact]
public void Catalog_must_not_depend_on_web()
{
Types().That().Are(Catalog)
.Should().NotDependOnAny(Web)
.Because("moduleprojecten mogen geen dependency op de Blazor Web-laag hebben")
.Check(Architecture);
}
}
Het voorbeeld is richtinggevend voor de regelvorm. De definitieve helpermethoden, assemblyselectie en packageversies worden in de implementatie vastgelegd zonder de hierboven vastgelegde architectuurregels te versoepelen.
23.10 Contract tests
Publieke contracten zijn de toegestane ingangen tussen modules. Een contracttest controleert niet alleen dat een methode bestaat, maar vooral dat het gedrag stabiel blijft voor andere modules.
Voorbeeldcontracten:
IRelationshipAccessReader
IRelationshipInvitationService
IExerciseRunResultReader
IMessageDispatcher
ITicketActionIndicatorReader
ISchedulingJobClient
Contract tests leggen minimaal vast:
- welke input geldig is;
- welke foutvorm bij ongeldige input wordt teruggegeven;
- of autorisatiecontext opnieuw wordt gecontroleerd;
- of idempotentie wordt ondersteund;
- welke outputvelden betrouwbaar gevuld zijn;
- of geen module-interne entities worden teruggegeven.
Voorbeeld:
[Fact]
public async Task RelationshipAccessReaderReturnsFalseWhenRelationshipIsInactive()
{
var reader = fixture.GetRequiredService<IRelationshipAccessReader>();
var allowed = await reader.HasActiveFriendshipAsync(studentA, studentB);
allowed.Should().BeFalse();
}
23.11 Cross-module integration tests
Cross-module integration tests zijn nodig wanneer één gebruikersactie meerdere modules raakt.
Voorbeelden:
| Workflow | Betrokken modules | Te testen |
|---|---|---|
| Relatie-uitnodiging versturen | Relationships, Communication | Uitnodiging en eventueel kritisch systeembericht ontstaan consistent of niet. |
| Accountprovisioning | Identity, Relationships, Communication | Pending uitnodigingen worden correct gekoppeld en vervolgcommunicatie ontstaat veilig. |
| Melding doorzetten naar docent | Support, Communication, Relationships | Ticketafsluiting, doorzetregistratie en namens-bericht blijven consistent. |
| Oefening delen | Practice, Relationships, Communication | Vriendschapscontrole, shared-record en communicatie volgen de workflowregels. |
| Live meekijken starten | LiveMonitoring, Practice, Authorization | Toegang wordt server-side gecontroleerd en LiveViewAudit wordt correct gevuld. |
Deze tests moeten ook foutscenario's bevatten:
- tweede kritieke stap faalt;
- gebruiker heeft geen toegang meer;
- relatie is net gedeactiveerd;
- run is niet afgerond;
- job wordt dubbel uitgevoerd;
- bericht of readmodel wordt opnieuw verwerkt;
- transactie wordt teruggedraaid.
23.12 Transaction boundary tests
Omdat cross-module workflows per geval bepalen welke stappen kritisch zijn, moeten transaction boundaries testbaar worden gemaakt.
Per workflow wordt minimaal vastgelegd:
| Vraag | Testverwachting |
|---|---|
| Welke stappen zijn kritisch? | Falen van een kritieke stap laat geen halve functionele toestand achter. |
| Welke stappen zijn retrybaar? | Falen van een retrybare stap zet een beheerbare pending/failed-status. |
| Is het systeembericht kritisch? | Als het bericht de primaire ingang is, faalt de workflow wanneer berichtcreatie faalt. |
| Is dubbele verwerking mogelijk? | Herhaalde uitvoering is idempotent of wordt veilig geweigerd. |
| Wat ziet de gebruiker bij falen? | Geen technische details, wel correcte foutafhandeling. |
Voorbeeld:
Relatie-uitnodiging versturen
- RelationshipInvitation aanmaken: kritisch
- SystemMessage aanmaken als primaire ontvangersingang: kritisch
- Badge/teller bijwerken: niet kritisch
Test:
- forceer fout bij SystemMessage-aanmaak
- verwacht: geen openstaande uitnodiging blijft achter
- verwacht: gebruiker krijgt veilige foutmelding
- verwacht: technische fout is gelogd met CorrelationId
23.13 Database-, migration- en seedtests
Databasegerelateerde tests bewaken dat de gekozen project-DbContext-schema-structuur correct blijft.
Minimaal te testen:
| Onderwerp | Test |
|---|---|
| Schema per module | Tabellen worden in het juiste schema aangemaakt. |
| Migrationvolgorde | Alle modulemigrations kunnen op een lege database worden toegepast. |
| Idempotente seeddata | Seeddata kan herhaald worden zonder duplicaten of ongewenste overschrijvingen. |
| Beheerbare content | Popups/templates worden niet blind teruggezet na beheerwijzigingen. |
| Soft links | Cross-module verwijzingen vereisen geen ongewenste harde FK. |
| Snapshots | Snapshotvelden worden gevuld op het afgesproken moment en niet stilzwijgend aangepast. |
| Anonimisering | Persoonsgegevens in snapshots worden volgens vaste regels vervangen. |
| Delete behavior | Historische data blijft beschikbaar waar functioneel vereist. |
Voor migrations wordt een aparte test gebruikt die een lege database opbouwt vanuit alle modulemigrations. Bij voorkeur wordt daarnaast een upgradepad getest vanuit een representatieve vorige baseline.
Voor EF Core migration history geldt aanvullend:
| Onderwerp | Testverwachting |
|---|---|
| History per schema | Iedere persistente DbContext gebruikt __EFMigrationsHistory in het eigen schema. |
| Geen centrale history | Er ontstaat geen gedeelde historytabel zonder contextonderscheid. |
| Deploymentvolgorde | Modulemigrations kunnen in de vastgelegde volgorde worden toegepast. |
| Upgradepad | Bestaande historytabellen worden niet stilzwijgend verplaatst of hernoemd. |
23.14 Seeddata en waardelijsttests
Seeddata en waardelijsten zijn extra gevoelig omdat zij vaak functionele betekenis hebben.
Voorbeelden:
| Module | Seeddata / waardelijst |
|---|---|
Identity | publieke rollen en niet-publieke rollen. |
Support | ticketstatussen en resolutietypen. |
Communication | systeemberichttemplates. |
Admin | popupdefinities, featuretoggles en systeeminstellingen. |
Catalog / ExerciseModuleHost | technische modulemetadata. |
Scheduling | jobtypen of schedulerconfiguratie waar van toepassing. |
Tests controleren:
- technische code blijft stabiel;
- gebruikerslabels kunnen veilig beheerbaar zijn waar toegestaan;
- verplichte waardes ontbreken niet;
- idempotente seed maakt geen duplicaten;
- seed overschrijft beheerwijzigingen niet zonder expliciete migratiekeuze.
23.15 Oefenmodulecontracttests
Concrete technische oefenmodules worden tegen een gemeenschappelijk contract getest. Hierdoor kan een nieuwe module worden toegevoegd zonder de kernapplicatie per module volledig te herschrijven.
Elke concrete module moet minimaal aantonen:
| Contractonderdeel | Testverwachting |
|---|---|
| Descriptor | Module heeft stabiele technische sleutel, naam, versie en capabilities. |
| Configuratie-DTO | Geldige configuratie wordt geaccepteerd; ongeldige configuratie geeft duidelijke validatiefouten. |
| Vraaggeneratie | Aantal vragen en configuratieregels worden gerespecteerd. |
| Antwoordcontrole | Goede, foute en lege antwoorden worden consistent beoordeeld. |
Geen idee | Indien ondersteund, telt dit functioneel als fout en bewaart juiste status. |
| Rendering | Vraag- en antwoordrepresentatie is bruikbaar voor UI. |
| PDF-representatie | Complexe notatie kan veilig aan PDF-export worden aangeboden. |
| Backwards compatibility | Historische payloads kunnen worden gelezen zolang dit volgens modulebeleid vereist is. |
Voorbeeldstructuur voor de eerste concrete module:
OefenHub.Modules.Arithmetic.AdditionSubtractionSimple.Tests/
Unit/
AdditionSubtractionAnswerEvaluatorTests.cs
AdditionSubtractionQuestionGeneratorTests.cs
Contracts/
AdditionSubtractionModuleContractTests.cs
TestData/
De eerste V1.0-module Optellen & Aftrekken (simpel) gebruikt het moduledossier Optellen & Aftrekken (simpel) als testinput voor configuratie, vraaggeneratie, antwoordvalidatie, schermstates, payloads en randgevallen.
Concrete modules mogen OefenHub.Modules.Abstractions gebruiken en alleen bewust OefenHub.SharedKernel. Tests moeten bewaken dat concrete modules geen afhankelijkheid nemen op Catalog, Practice, Web of module-interne platformimplementaties.
23.16 Autorisatie- en contexttests
Autorisatie is server-side en mag niet door clientstate, routeparameters of zichtbare knoppen worden afgedwongen. Tests moeten daarom niet alleen happy paths testen, maar ook route-manipulatie, oude browsercontext en combinatierollen.
Minimaal te testen:
| Context | Voorbeeldtest |
|---|---|
| Leerling | Oefening starten faalt wanneer niveauautorisatie vervalt. |
| Docent | Docent ziet alleen resultaten binnen eigen docentcontext. |
| Ouder/voogd | Resultaatdetail vereist actieve GuardianStudent-relatie bij iedere actie. |
| Beheerder | Beheercontext wordt server-side bepaald, niet via routeparameter. |
| Combinatierol | Docent/ouder-contexten lekken geen autorisatie naar elkaar. |
| Accountstatus | IsActive = false blokkeert toegang maar verwijdert geen historie. |
| Live meekijken | Live-start maakt alleen audit aan na geldige autorisatiecontrole. |
| PDF-export | Run-ID alleen is nooit genoeg; export hercontroleert toegang. |
Autorisatietests horen deels in OefenHub.Authorization.Tests en deels in module- of integratietests waar objecttoegang concreet wordt toegepast.
23.17 Web-, component- en UI-compositietests
OefenHub.Web bevat routing, layouts, componenten, formulieren, viewmodels en page composition. Web mag geen DbContexts of module-interne entities gebruiken. Tests voor Web richten zich daarom op UI-compositie en interactie, niet op domeinregels.
Te testen onderwerpen:
- layout- en navigatiekeuzes per rolcontext;
- afleidingsvrije oefenrunweergave;
- formulierbinding en validatiemeldingen;
- correcte aanroep van publieke modulecontracten;
- geen directe DbContext-injectie in pages/components;
- lege toestanden;
- veilige foutweergave;
- responsive componentgedrag waar technisch testbaar.
Voorbeeld:
Teacher frontpage composition
- Web roept Catalog-query aan voor niveausamenvatting
- Web roept Practice-query aan voor resultaatindicaties
- Web bouwt één viewmodel
- Web leest geen catalog/practice DbContext rechtstreeks
UI-labels en schermspecifieke details blijven primair in schermdocumentatie; dit hoofdstuk beschrijft de technische testaanpak.
23.17.1 Toolkeuze voor component- en end-to-endtests
De V1.0-baseline gebruikt twee aanvullende frontendtestlijnen:
| Testlijn | Tooling | Project | Scope |
|---|---|---|---|
| Blazor componenttests | bUnit | OefenHub.Web.Tests | OefenHub-wrappercomponenten, componentcatalogus, parameters, events, validatie, loading/empty/error states en page composition zonder browser. |
| End-to-endtests | Playwright .NET | OefenHub.EndToEndTests | Beperkte smoke- en regressietests voor primaire gebruikersflows in echte browsercontext. |
Componenttests zijn de standaard voor herbruikbare UI-bouwblokken. Zij moeten voorkomen dat pagina's opnieuw zelfstandige HTML-/CSS-opbouw krijgen in plaats van gedeelde OefenHub-componenten.
End-to-endtests blijven bewust beperkt. Zij dekken de belangrijkste happy paths en enkele kritieke fout-/autorisatiescenario's, maar vervangen geen module-, contract-, autorisatie- of componenttests. E2E-tests mogen daarom niet de primaire plek worden voor alle domeinlogica.
Minimale E2E-dekking voor V1.0:
| Flow | Minimale dekking |
|---|---|
| Login en rolcontext | Testgebruiker kan inloggen of via testauth worden geplaatst en juiste rolcontext openen. |
| Leerling oefening starten en afronden | Oefening starten, antwoord registreren, resultaat bereiken. |
| Docent dashboard en leerlingoverzicht | Frontpage/overzicht laadt met testdata en toont toegestane acties. |
| Ouder-/voogd kindoverzicht | Gekoppeld kind zichtbaar; niet-gelinkte data niet zichtbaar. |
| Beheerder kernbeheer | Minimaal één beheerbaar content-/instellingenscherm opent en valideert veilig. |
| PDF-download smoke | Geautoriseerde export levert downloadrespons zonder inhoudelijke PDF-pixelvergelijking. |
| Live meekijken smoke | Geautoriseerde viewer ziet live-status of veilige fallback; reconnectdetails blijven primair component-/integratietest. |
De exacte testframeworkkeuze voor xUnit, NUnit of MSTest blijft centraal voor alle testprojecten gelden. bUnit en Playwright worden gebruikt binnen die gekozen testframeworklijn.
23.18 SignalR- en live-meekijktests
Live meekijken gebruikt SignalR als transport, maar server-side opgeslagen voortgang als bron van waarheid. Tests moeten beide aspecten onderscheiden.
| Onderwerp | Testverwachting |
|---|---|
| Bron van waarheid | Na bevestigd antwoord is voortgang server-side opgeslagen voordat updates worden gebruikt. |
| Autorisatie | Viewer mag alleen starten binnen geldige docent- of ouder-/voogdcontext. |
| LiveViewAudit | Bewuste live-start maakt auditrecord; alleen online-overzicht openen niet. |
| Reconnect | Na reconnect wordt actuele server-side voortgang opnieuw gelezen. |
| Reconnectdelays | 0, 2, 10, 30 en 60 seconden; na 5 automatische pogingen veilige verbroken status. |
| Sessie-einde | Verlaat leerling de oefening, dan eindigt liveweergave veilig. |
| Browse-modus | Terug naar live gebruikt actuele server-side vraagstatus. |
| Gelijktijdige viewers | Meerdere geautoriseerde meekijkers wijzigen de run niet. |
SignalR-tests mogen deels component-/integratietests zijn. Niet alle timingdetails hoeven als fragiele end-to-endtest te worden vastgelegd, maar de reconnectstatussen, foutmelding en herlaadactie moeten component- of integratietestbaar zijn.
23.19 QuestPDF- en exporttests
PDF-export is technisch belangrijk omdat de output historisch consistent moet zijn en gebaseerd wordt op opgeslagen runcontext en snapshots. OefenHub gebruikt daarom een vaste PDF-regressieteststrategie in OefenHub.Reporting.Tests.
23.19.1 Testdoelen
Te testen onderwerpen:
- veilige bestandsnaamlogica;
- gebruik van historische runcontext;
- geen afhankelijkheid van actuele naamgeving wanneer snapshot leidend is;
- verplichte secties, totalen en contextlabels;
- tabelheaders op vervolgpagina's;
- geen rij-splitsing waar dat functioneel is uitgesloten;
- footeropbouw en paginanummering;
- module-specifieke vraag-/antwoordrepresentatie;
- fallbackrepresentatie bij module- of payloadproblemen;
- tijdelijke bestandsopslag en cleanup;
- autorisatiehercontrole bij export;
- foutafhandeling zonder dataverlies;
- privacy: geen persoonsgegevens buiten toegestane context en geen payloads in logs.
23.19.2 Tooling
De PDF-regressietestbaseline gebruikt:
| Onderdeel | Tooling | Doel |
|---|---|---|
| PDF-rendering | QuestPDF | Productiepad en testpad gebruiken dezelfde documentopbouw. |
| Snapshotasserties | Verify | Vergelijken van genormaliseerde tekst-, metadata-, objectmodel- en eventueel image-output. |
| Tekstextractie | PdfPig of Verify.PdfPig | Controleren van PDF-inhoud zonder volledige binaire vergelijking. |
| Testadapter | Adapter bij gekozen testframework | Bijvoorbeeld Verify.Xunit bij xUnit. |
| Visuele kernsnapshots | QuestPDF image- of SVG-output waar stabiel toepasbaar | Alleen voor geselecteerde kernlayouts. |
Packageversies worden centraal vastgepind zodra het testframework is gekozen. De strategische keuze voor Verify/PdfPig is onderdeel van de baseline.
23.19.3 Structurele regressietests
Structurele regressietests zijn de standaard. Zij maken van een gegenereerde PDF een genormaliseerd testartefact en controleren bijvoorbeeld:
- pagina-aantal;
- documenttitel of downloadcontext;
- aanwezigheid en volgorde van kernsecties;
- tekstinhoud van samenvatting, resultaattabel en statistieken;
- aanwezigheid van headers en footers per pagina;
- geselecteerde metadata, na normalisatie van niet-deterministische waarden;
- afwezigheid van verboden tekst, zoals technische foutdetails of ruwe payloadvelden.
Volledige bytevergelijking van PDF-bestanden is geen geldige primaire regressietest, omdat PDF-output metadata, ordering of renderingdetails kan bevatten die functioneel niet relevant zijn.
23.19.4 Beperkte visuele regressie
Visuele regressie wordt beperkt toegepast voor enkele stabiele kernlayouts:
| Layout | Doel |
|---|---|
| Eénpagina-resultaat | Basale typografie, header, samenvatting en footer. |
| Meerpagina-resultaat | Paginering, herhaalde tabelheaders en footerpagina's. |
| Rijke moduleweergave | Module-specifieke notatie zonder layoutbreuk. |
| Fallbackweergave | Veilige fallbacktekst zonder visuele ontsporing. |
Deze tests draaien bij voorkeur in een vaste runtime/container met vaste fonts. Ze hoeven niet in iedere lokale snelle testlus te draaien. CI mag onderscheid maken tussen pull-requestchecks, nightly checks en releasechecks.
23.19.5 Snapshotbeheer
Voor snapshotbeheer gelden aanvullende regels:
- snapshots bevatten alleen fictieve of geanonimiseerde testdata;
- klok, cultuur, tijdzone en testfixtures zijn deterministisch;
- niet-deterministische velden worden vooraf genormaliseerd;
- snapshotupdates vereisen review en een duidelijke reden;
- ontvangen testoutput wordt niet automatisch gecommit;
- visuele snapshots worden niet gebruikt om brede ontwerpwijzigingen te accorderen zonder inhoudelijke review.
23.20 Scheduling-, TickerQ- en jobtests
Scheduling heeft een eigen project en bij voorkeur een eigen schema. Tests moeten bewaken dat jobs persistent, herleidbaar en beheerbaar zijn.
Te testen onderwerpen:
| Onderwerp | Testverwachting |
|---|---|
| Jobaanmaak | Domeinmodule kan via schedulingcontract een job aanvragen. |
| Persistence | Herstart van de applicatie verliest geplande of lopende jobs niet. |
| Jobtype | Alleen bekende jobtypes worden uitgevoerd. |
| Retrybeleid | Retryconfiguratie wordt per jobtype toegepast en is begrensd. |
| Idempotentie | Dubbele uitvoering veroorzaakt geen dubbele functionele mutatie. |
| Failed status | Na maximale retries ontstaat een beheerbare failed-status. |
| Correlation | JobId en CorrelationId zijn door de hele keten herleidbaar. |
| Dashboard | TickerQ-webinterface is alleen intern beschikbaar. |
| Domeinuitvoering | Scheduling roept domeinacties alleen via publieke contracten aan. |
Bij multi-domain jobs moet expliciet worden getest of de gekozen foutstrategie correct werkt: atomair, compensatie, retry of beheerbare failed-status.
23.20.1 Mailafhandelingstests
Mailafhandeling wordt getest als ondersteunende providerintegratie en als retrybare of kritieke workflowstap waar dat functioneel is vastgelegd.
| Testgebied | Te bewijzen gedrag |
|---|---|
| Mailproviderconfiguratie | Ontbrekende of foutieve providerconfiguratie faalt veilig zonder secrets te lekken. |
| Maildelivery job | Retrybeleid, idempotency key, failed-status en correlation-id werken correct. |
| Mailfout na bronmutatie | Brondata blijft consistent en de gebruiker ziet geen misleidende rollbackstatus. |
| Kritieke mailaanvraag | Workflow faalt atomair wanneer mailaanvraag functioneel noodzakelijk is en niet veilig kan worden vastgelegd. |
| Mailinhoud | Geen secrets, tokens, wachtwoorden, ruwe modulepayloads of ongeautoriseerde domeindata. |
23.21 Logging-, correlation- en foutafhandelingstests
Loggingtests richten zich niet op exacte tekstregels, maar op aanwezigheid van noodzakelijke context en afwezigheid van gevoelige inhoud.
Minimaal te testen:
- iedere request krijgt of behoudt een
CorrelationId; - cross-module workflows geven
CorrelationIddoor; - jobs loggen
JobId,JobType, pogingnummer en status; - access-denied wordt gelogd zonder inhoudelijke data te lekken;
- technische foutpagina's tonen geen stacktrace of SQL-details;
- logs bevatten geen tokens, wachtwoorden, credentials of volledige payloads;
- foutafhandeling maakt onderscheid tussen validatiefout, autorisatiefout, niet gevonden en technische fout.
Voorbeeld:
PDF-export zonder toegang
- gebruiker vraagt run van niet-gekoppeld kind op
- response toont veilige toegang-geweigerdafhandeling
- log bevat CorrelationId, actor, rolcontext en foutcategorie
- log bevat geen vraaginhoud, antwoorden of PDF-payload
23.22 Security- en configuratietests
Omdat security geen apart project heeft, moeten securitychecks verspreid maar expliciet in de teststrategie blijven.
Te testen of te controleren:
| Onderwerp | Test/controle |
|---|---|
| HSTS | Actief in productieachtige omgeving, niet onbedoeld lokaal blokkerend. |
| CSP | Vastgelegde CSP-baseline aanwezig en passend bij Blazor, SignalR, MudBlazor, fonts, images en PDF-downloads. |
| Securityheaders | Verwachte headers worden gezet via pipeline/middleware. |
| Cookies | Secure, HttpOnly en SameSite waar passend. |
| Browser storage | Geen tokens, credentials, persoonsgegevens of autorisatiedata. |
| Rate limiting | Routecategorieën gebruiken de vastgelegde limieten; misbruikscenario's worden begrensd zonder normale flows te blokkeren. |
| Identity callback | Ongeldige of onvolledige provisioning geeft geen gedeeltelijke sessie. |
| Authorization policies | Policies falen gesloten bij ontbrekende context. |
| TickerQ-dashboard | Alleen intern en geautoriseerd bereikbaar. |
| Secrets | Verplichte secrets ontbreken niet en worden niet uit code of testdata geladen. |
Sommige controles zijn pipeline-/integratietests; andere horen als releasecheck of operationele controle in CI/CD.
23.23 Performance- en beschikbaarheidstests
Performance wordt gericht getest op bekende risicogebieden. Er wordt niet geprobeerd om elke functie kunstmatig te loadtesten of top-tier latency af te dwingen. De V1.0-grenzen uit hoofdstuk 22 zijn voldoende voor acceptabele performance bij representatieve belasting.
Richtgebieden:
- leerling oefenrun met directe opslag na ieder bevestigd antwoord;
- geschiedenisqueries met filters en paginering;
- ouder-/voogdresultaten over alle niveaus;
- docentoverzichten met leerlingen en autorisaties;
- berichtenbadges en meldingenindicaties;
- SignalR live meekijken met meerdere viewers;
- PDF-export van lange resultaatsets;
- TickerQ-verwerking van verlopen heropentermijnen, uitnodigingen en cleanup;
- frontpage-samenvattingsblokken.
Minimale performancevalidatie:
| Testgebied | Minimale validatie | Acceptatiegrens |
|---|---|---|
| Oefenrun | startflow en antwoord bevestigen met representatieve moduledata | grenzen uit 22.5.1 |
| Frontpages en dashboards | eerste pagina laden met realistische aantallen kinderen, leerlingen, tickets en badges | grenzen uit 22.5.1 |
| Overzichten | filteren, sorteren en pagineren met groeigevoelige datasets | standaard 25 en maximaal 100 regels per pagina |
| Geschiedenis en resultaten | detailpagina, samenvatting en gefilterde historie | geen volledige onbeperkte dataset in één response |
| Live meekijken | voortgang publiceren na opgeslagen antwoord | p95 ≤ 3 s voor updatezichtbaarheid; reconnectinstellingen apart valideren |
| PDF-export | normale resultaatset en bovengrensscenario | normale export p95 ≤ 60 s; grote export begrenzen of jobmatig verwerken |
| Beheeranalyse | logs, supportoverzichten en beheerfilters | p95 ≤ 8 s met verplichte filters/paginering |
| Jobs | niet-kritieke side-effects en cleanup | normaal binnen geconfigureerde interval en beheerbaar bij failed-status |
De testset gebruikt fictieve of geanonimiseerde data. Structurele overschrijding leidt eerst tot queryoptimalisatie, indexering, readmodel/caching, payloadverkleining, paginering of jobmatige verwerking binnen de bestaande modulaire monoliet. Extra infrastructuur, microservices of high-availabilitymaatregelen zijn geen standaardreactie op een performanceoverschrijding in V1.0.
23.24 Testdata en scenariofixtures
Testdata moet representatief zijn zonder echte persoonsgegevens te gebruiken.
Minimale scenariofixtures:
| Fixture | Doel |
|---|---|
| Leerling met actief niveau | Oefenrun-, frontpage- en geschiedenisflows. |
| Leerling zonder niveau | Profiel-/niveaublokkade. |
| Docent met eigen leerling | Autorisatie, resultaten en live meekijken. |
| Docent zonder toegang | Toegang-geweigerdscenario's. |
| Ouder/voogd met gekoppeld kind | Resultaten, geschiedenis en live meekijken. |
| Ouder/voogd na ontkoppeling | Direct vervallen toegang. |
| Beheerder | Beheerflows en supportcorrecties. |
| Combinatierol | Contextscheiding tussen beheerder, docent en ouder/voogd. |
| Afgeronde run | Resultaat, geschiedenis, PDF en statistieken. |
| Niet-afgeronde run | Hervatten en live voortgang. |
| Testrun docent | Uitsluiting van reguliere geschiedenis en cleanup. |
| Gedeelde oefening | Ontvangst, starten en eigen runvorming. |
| Ticket met heropentermijn | Opgelost/Gesloten-afleiding en schedulerverwerking. |
Testdata wordt bij voorkeur opgebouwd via builders in plaats van grote ondoorzichtige databasedumps.
23.25 Traceability voor requirements, acceptatiecriteria en module-eisen
Traceability wordt niet alleen administratief bijgehouden, maar ook gebruikt om ontbrekende testdekking zichtbaar te maken.
Per centrale eis uit de Software Requirements Specification wordt minimaal één van de volgende statussen vastgelegd:
| Status | Betekenis |
|---|---|
| Automatisch getest | Er bestaat een unit-, integratie-, contract-, architecture-, component- of E2E-test. |
| Handmatig getest | De eis vereist handmatige acceptatie of visuele controle. |
| Operationeel geborgd | De eis wordt via deployment, beheerprocedure, monitoring of configuratie geborgd. |
| Beleidsmatig geborgd | De eis is een proces-/documentatieregel en niet zinvol automatisch testbaar. |
| Nog te dekken | Er is nog geen passende test of borging vastgelegd. |
Voor module-eisen geldt hetzelfde, maar in het aparte module-eisenregister moet zichtbaar blijven welke concrete module het contract of de concrete module-eis afdekt.
Voorbeeld:
| Eis | Dekkingstype | Testlocatie |
|---|---|---|
| Autorisatie ouderresultaat | Integratietest | OefenHub.Practice.Tests/Integration |
| Moduleantwoordcontrole | Modulecontracttest | OefenHub.Modules.Arithmetic.Addition.Tests/Contracts |
| Geen directe Web-toegang tot DbContext | Architecture test | OefenHub.ArchitectureTests |
| PDF-bestandsnaamlogica | Unit/integratietest | OefenHub.Reporting.Tests |
| TickerQ-dashboard intern | Security/configuratiecheck | OefenHub.IntegrationTests of releasecheck |
23.26 Definition of Done voor technische wijzigingen
Een technische wijziging is pas afgerond wanneer de relevante test- en documentatie-impact is beoordeeld.
Minimale checklist:
| Controle | Verwachting |
|---|---|
| Module-eigenaarschap | Duidelijk welke module eigenaar is van code, data en tests. |
| Testdekking | Unit-, integratie-, contract-, architecture- of handmatige test gekozen waar passend. |
| Cross-module impact | Publieke contracten, workflows en transaction boundaries beoordeeld. |
| Database-impact | DbContext, schema, migration, seeddata en soft link/snapshotbeleid gecontroleerd. |
| Security-impact | Autorisatie, logging, secrets, headers, cookies of rate limits beoordeeld waar relevant. |
| Privacy-impact | Persoonsgegevens, snapshots, logs, exports en testdata gecontroleerd. |
| Software Requirements Specification en acceptatiecriteria-impact | Requirement- en acceptatiecriteriadekking bijgewerkt of gemotiveerd. |
| Documentatie-impact | Functioneel Ontwerp, Software Requirements Specification, Technisch Ontwerp, database-informatie, ERD, schermdocumentatie en usecases beoordeeld. |
| CI | Relevante testsets draaien groen. |
| Regressierisico | Kritieke hoofdflows blijven afgedekt. |
23.27 CI/CD kwaliteitsgrenzen
De exacte pipeline wordt in het beheer- en deploymenthoofdstuk uitgewerkt; de teststrategie vereist minimaal deze kwaliteitsgrenzen.
| Pipelinefase | Minimale checks |
|---|---|
| Build | Alle projecten compileren, warnings volgens afgesproken beleid. |
| Unit tests | Module-unit tests draaien snel en deterministisch. |
| Architecture tests | Dependencyregels en projectgrenzen worden gecontroleerd. |
| Integration tests | Geselecteerde module- en cross-module tests draaien tegen testdatabase. |
| Migration check | Migrations kunnen op lege database worden toegepast. |
| Security/config check | Basisheaders, cookies, secretsvalidatie en verboden configuratie worden gecontroleerd. |
| Modulecontracttests | Concrete oefenmodules voldoen aan abstractions en contractverwachtingen. |
| Smoke tests | Kritieke applicatiepaden worden na deployment kort gecontroleerd. |
Niet elke zware integratie- of performance-test hoeft bij iedere lokale build te draaien. CI maakt onderscheid tussen snelle checks, pull-requestchecks, nightly checks en releasechecks.
23.28 Kwaliteitsgrenzen en bewuste beperkingen
De teststrategie legt ook vast wat niet wordt nagestreefd.
| Geen doel | Reden |
|---|---|
| Alles via E2E testen | Te traag, fragiel en onvoldoende gericht op modulegrenzen. |
| Volledige pixel-perfect PDF-dekking voor alle scenario's | Structurele PDF-correctheid is leidend; visuele snapshots blijven beperkt tot stabiele kernlayouts. |
| Volledige productie-load simuleren vanaf dag één | Eerst representatieve risicoscenario's bepalen. |
| Echte identity-provider volledig mockloos testen in alle tests | Contract- en integratietests worden gescheiden van volledige externe flowtests. |
| Oneindige retries testen als succespad | Retries zijn begrensd en failed-status is een geldige uitkomst. |
| Productiedata gebruiken voor testdata | Niet toegestaan vanwege privacy en dataveiligheid. |
23.29 Implementatieverificaties
De volgende implementatieverificaties moeten vóór of tijdens realisatie expliciet worden afgerond:
| Punt | Te bepalen |
|---|---|
| Testframework | Definitieve keuze voor xUnit/NUnit/MSTest en assertion library. Moq is de baseline mockinglibrary; packageversie en security-/dependencyreview worden centraal gevalideerd. |
| ArchUnitNET-inrichting | Packageversie, testadapter, centrale assemblyloader en eerste regelset inrichten. |
| Testdatabase | Keuze tussen SQL Server container, LocalDB of andere testdatabase. |
| Migration teststrategie | Lege-database- en upgradepadtests inrichten inclusief migration history per schema. |
| bUnit/Playwright-inrichting | Packageversies, testfixtures, testauth en CI-browserinstallatie inrichten. |
| SignalR-testaanpak | Reconnectstatussen, foutmelding en herstel uit server-side brondata automatiseren. |
| TickerQ-testaanpak | Hoe persistentie, dashboardafscherming en retries geautomatiseerd worden getest. |
| Traceabilityregister | Waar testdekking per Software Requirements Specification en acceptatiecriteria/module-eis praktisch wordt bijgehouden. |
| Performancecriteria | Concrete grenswaarden zodra datasets en hostingprofiel bekend zijn. |