Dit hoofdstuk beschrijft hoe er binnen MOZA getest wordt en bestaat uit verschillende delen.
Een teststrategie die voor alle MOZA-services geldt en een testplan waarin de strategie uitgewerkt is voor de backendservices.
Indien er een UI aan MOZA wordt toegevoegd dient hier een additioneel testplan voor te worden toegevoegd.
De teststrategie beschrijft de keuzes die bepalen waarom we testen, tegen welke risico's we die inspanning inzetten, hoeveel test een wijziging moet dragen en wie de eigenaar is.
Deze strategie geldt voor iedere MOZA-service.
Binnen scope: geautomatiseerd en exploratief testen van de MOZA-services zelf.
Dit betreft het gedrag, de gegevens, de API's, en de afspraken die de services met elkaar en met externe partijen hebben.
Buiten scope:
Aanname: deterministische services
Deze strategie gaat ervan uit dat dezelfde invoer dezelfde uitvoer oplevert en dat een test die uitvoer kan asserteren.
Voor een component die zijn antwoord laat genereren of zelf zijn bron kiest vervalt die aanname.
Asserteren op gelijkheid kan daar niet en gebruikersinvoer wordt er een stuurmiddel in plaats van alleen data.
Zo'n component valt onder hetzelfde risicobeeld en dezelfde lagen maar vereist een aanvulling op deze strategie voordat hij in productie gaat.
Het risicobeeld wordt bepaald door wat de MOZA-backendservices met elkaar doen.
Zij leggen vast hoe iemand door de overheid benaderd wil worden, verstrekken die gegevens aan overheidsorganisaties die erom vragen, leveren op die basis berichten af bij de ontvanger, en melden over die aflevering terug aan de afzender.
De focus is met name op de gevallen waarin het systeem gewoon doorloopt en een verkeerde, onrechtmatige of onzichtbare uitkomst produceert. De testinspanning is daarop afgesteld.
| # | Risico | Waarom | Afgedekt door |
|---|---|---|---|
| R1 | Verkeerd, ongewenst of herhaald contact: een bericht aan de verkeerde persoon, via een ingetrokken kanaal, of twee keer omdat een retry het dubbel aflevert. | Directe schade voor een burger en voor het vertrouwen in de overheid; niet terug te draaien zodra het verzonden is. | Unit (de beslissing), component (de routering en de idempotentie van aflevering), e2e (de journey). |
| R2 | Onrechtmatige verstrekking of bewaring van persoonsgegevens. | Een juridische inbreuk, en een die een functionele suite vrolijk groen noemt. | Component (scopefiltering, retentiejobs), misbruikcases. |
| R3 | Ontbrekende of onjuiste verwerkingslogging. | Verwerkingen loggen is een wettelijke verplichting. Het ontbreken ervan is onzichtbaar bij normaal gebruik. | Component, geassert als uitkomst en nooit als implementatiedetail. |
| R4 | Een afspraak met een ander team of partij breken. | Breekt de service van een ander bij hún deploy, niet bij de onze. Laat en duur gevonden. | Contract |
| R5 | Stille storingen. Bijv.: jobs die niet meer draaien, retries die uitblijven, callbacks die verdwijnen. | Er wordt niets rood. De schade stapelt zich op tot iemand het handmatig opmerkt. | Component met gestuurde tijd, en een geasserteerd signaal waarop de alertering kan aanhaken. |
| R6 | Onbeschikbaarheid of degradatie onder realistische load. | Burgers kunnen een dienst die ze moeten gebruiken niet bereiken. | Performance, tegen een vastgestelde drempel. |
| R7 | Compromittering via niet-vertrouwde invoer. Bijvoorbeeld request forgery via door de aanroeper aangeleverde URL's, omzeilen van authenticatie, verzwakte pseudonimisering. | Extern bereikbaar en extern vindbaar. | Misbruikcases en fuzzing; niet door scanners alleen. |
| R8 | Onjuiste terugmelding over aflevering: de afzender krijgt te horen dat een bericht is bezorgd terwijl dat niet zo is, of omgekeerd. | De afzender handelt naar die melding en stuurt geen herinnering, terwijl de ontvanger niets heeft gekregen. Niets wordt rood. Het verschil komt pas aan het licht bij een klacht of een gemiste termijn. | Component (de statusovergangen en de callback), contract (de vorm van de statusmelding), e2e (de journey inclusief de asynchrone bezorgstatus). |
| R9 | Onterecht uitgebleven uitkomst: een filter-, scope- of retentieregel die correct draait en ten onrechte iets weglaat, waardoor een bericht, gegeven of melding niet komt. | Er gaat niets stuk en er wordt niets rood. De ontvanger weet niet wat hij niet heeft gekregen. Het komt pas aan het licht bij een klacht of gemiste termijn. | Unit (de insluitingsregel, met expliciet het geval dat er wél door moet), component (scopefiltering en retentie vanaf de buitenkant). |
Onderstaande toont hoe de kwaliteitsattributen aan bod komen.
| Kwaliteitsattribuut | Aandacht | Hoe |
|---|---|---|
| Functionele juistheid | Hoog | De hele piramide. Dit betreft het grootste deel van de inspanning. |
| Security | Hoog | Misbruikcases per uitgaande aanroep en per authenticatiepunt, fuzzing, dependency-scanning. |
| Betrouwbaarheid (fouttolerantie, herstel) | Hoog | Fault-injection in de componentlaag; geen aparte robuustheidssuite. |
| Compatibiliteit / interoperabiliteit | Hoog | Contracttesten; conformiteit aan de specificatie. |
| Performance-efficiëntie | Middel, drempelgestuurd | Alleen tegen een vastgesteld belastingmodel en budget. Zonder die twee is het een rapport, geen test. |
| Onderhoudbaarheid | Middel, indirect gemeten | Via de testsuite zelf: looptijd, flakiness, mutation score. |
| Bruikbaarheid, overdraagbaarheid | Hier niet getest | Zie Reikwijdte |
Onderstaand staan de uitgangspunten waarop deze teststrategie is neergezet. Deze gelden ongeacht taal, framework of CI-systeem en zijn dus op iedere bestaande en nog te bouwen service van toepassing.
unit staat.Niet iedere wijziging verdient dezelfde behandeling.
Alles licht behandelen laat de benoemde risico's onbeschermd. Alles zwaar behandelen maakt de suite zo traag dat hij wordt omzeild, waarmee je diezelfde bescherming alsnog verliest.
De auteur van een wijziging kiest de klasse. De reviewer toetst die keuze.
Klasse A: raakt een benoemd risico.
Persoonsgegevens, contactroutering, authenticatie of autorisatie, verwerkingslogging, retentie of verwijdering, een uitgaande aanroep, of alles wat zichtbaar is in een gepubliceerde API.
Vereist:
Klasse B: gewoon gedrag.
Businesslogica en endpoints zonder impact op persoonsgegevens, security of contract.
Vereist:
Klasse C: geen gedragswijziging.
Documentatie, opmaak, een dependency-bump die geen API verandert.
Vereist:
Is de klasse niet evident, dan is het klasse A.
De kosten van één wijziging teveel testen zijn te overzien. De kosten van een R1- of R2-wijziging te weinig testen zijn exponentieel hoger.
Belangrijke punten om hierin mee te nemen die niet omlaag schalen zijn uitgangspunt 9 (elke bevinding krijgt een regressietest) en de determinismeregels.
Die gelden voor klasse C net zo goed als voor klasse A.
Vier functionele lagen, elk met één vraag, plus kwaliteitschecks die naast de piramide draaien in plaats van erin. Details als de mechanismen, het gereedschap en de frequenties staan in het testplan.
Functioneel en acceptatietesten is geen vijfde laag (zie uitgangspunt 1).
Toewijzingsregel:
Het team dat de service bezit, bezit de tests
Dit is inclusief de tests die falen om redenen buiten hun eigen code.
De auteur classificeert, de reviewer toetst (zie Risicoklassen/Testdiepgang).
Een pull request waarvan de risicoklasse niet uit de wijziging is af te leiden, is niet klaar voor review.
Een rode nachtrun heeft de volgende werkdag een eigenaar.
Wie dat is rouleert, en dat rooster staat per service opgeschreven in plaats van verondersteld te worden.
Een gequarantainede test is een issue met een eigenaar en een deadline.
Quarantaine zonder deadline is verwijderen met extra stappen, en oneerlijker, omdat de test nog in de lijst staat.
Contracteigenaarschap is gesplitst
De provider bezit de specificatie, elke consumer bezit zijn eigen verwachtingen.
Geen van beide past het artefact van de ander aan om een build groen te krijgen.
Securitybevindingen
Deze worden ingediend door wie ze ontvangt en opgelost door het eigenaarsteam, dat ook de regressietest schrijft (uitgangspunt 9).
Geen productiedata in enige testomgeving, op geen enkele laag.
Testdata is synthetisch en wordt opgebouwd door de test die de data nodig heeft. Dat is om R2-redenen niet onderhandelbaar en tevens goedkoper.
Elke test bezit zijn eigen data en assert alleen op die data.
Een gedeelde fixture koppelt tests die geen reden hebben van elkaar af te weten.
En een assertie op een totaal is een assertie over elke andere test in de suite.
De PR-laag draait op een laptop (uitgangspunt 10).
Heeft de test een omgeving nodig, dan zit hij in de verkeerde laag.
Alleen de end-to-end-laag hangt van een gedeployde omgeving af
De e2e laag moet kunnen falen zonder een merge te blokkeren. Anders legt een omgevingsprobleem alle levering stil.
Welke omgevingen er zijn en hoe erop wordt uitgerold, staat in Deployment.
Klaar om te mergen: de verplichte PR-checks zijn groen en de wijziging draagt de behandeling die de betreffende klasse vereist. Er is geen verificatiestap na de merge.
Klaar om te releasen: de nachtsuite is groen, of de gefaalde testen zijn beoordeeld op urgentie en het besluit om toch te releasen is vastgelegd met een naam eronder. Het contract van elke consumer verifieert tegen de versie die uitgaat. Er staat geen openstaande security- of R1/R2-bevinding op het gebied dat wordt gewijzigd.
Doorlopend, los van een wijziging: elk risico uit het Risicobeeld is aanwijsbaar met minstens één test die hem afdekt, per service waarvoor het risico geldt.
De risicotabel noemt een laag. Deze eis vraagt een test. Zonder dat wordt een risico alleen geverifieerd wanneer iemand dat gebied toevallig wijzigt.
Is een risico voor een service niet van toepassing, dan wordt dat vastgelegd in plaats van stilzwijgend overgeslagen.
Klaar om een nieuwe service in MOZA op te nemen: die voldoet vanaf de eerste pull request aan de definitions of done uit het plan, in plaats van later een testsuite te krijgen. Een unitlaag achteraf inbouwen in een service die zonder is ontworpen, is de duurste ingreep die er is.
Een kleine set indicatoren, elk gekoppeld aan een beslissing.
De set wordt maandelijks besproken door wie op dat moment de nachtrun-rotatie draait.
De uitkomst is een aanpassing in de teststrategie of een issue, nooit een rapport.
| Indicator | Vertelt ons | Actie erop |
|---|---|---|
| Defecten gevonden na de merge, met de laag erbij die ze had moeten vangen. | Welke laag te dun is. | Tests toevoegen op díe laag, niet op de laag die het vond. |
| Doorlooptijd van de verplichte PR-checks (streven onder 15 minuten). | Of de componentlaag zijn budget ontgroeid is. | Gevallen omlaag duwen naar de unitlaag. |
| Aantal flaky en gequarantainede tests. | Of de suite nog vertrouwd wordt (uitgangspunt 5). | Repareren of verwijderen, nooit negeren. |
| Mutation score op de unitlaag, zodra die laag bestaat. | Of unittests asserten of alleen uitvoeren. | Asserties aanscherpen. |
| Coverage | Context bij het bovenstaande; nooit een doel op zich (uitgangspunt 8) | Vragen waarom een daling optrad. |
| Risico's zonder aanwijsbare test, per service. | Of de dekking van het risicobeeld nog klopt (zie Afrondingscriteria). | Streven is nul. Iedere andere uitkomst is een test schrijven of het risico expliciet niet-toepasselijk verklaren. |
De doorlooptijd van de PR-checks en het aantal gequarantainede tests komen uit de pipeline en de issuelijst; die slaan zelf alarm. De overige vier vragen een blik.
Indicator 1 vereist één afspraak: een defect dat na de merge wordt gevonden, krijgt bij het sluiten de laag erbij die het had moeten vangen. Dat is dezelfde afweging die uitgangspunt 9 al vraagt bij het schrijven van de regressietest.
Deze strategie wordt, net als code, alleen aangepast met een PR inclusief een opgegeven reden.
Afwijken mag, stil afwijken niet.
Spreken de werkelijkheid en deze strategie elkaar tegen, dan wijzigt óf de strategie, óf het verschil wordt vastgelegd met een eigenaar erbij.
Deze strategie dient door het team te worden herzien wanneer een service aan MOZA wordt toegevoegd, wanneer het platform wezenlijk verandert, en verder minstens jaarlijks.
ADR-0017 Inrichting testen MOZa beschrijft een eerder testkader. Deze strategie en het testplan hieronder wijken daar op onderstaande punten bewust van af.
Er dient bepaald te worden of ADR-0017 hiermee vervalt of opgevolgd dient te worden door een nieuw ADR.
Tot dan legt deze sectie de afwijkingen vast.
Nog niet besloten, en daarom niet stilzwijgend vervallen:
Beide staan in ADR-0017 en hebben in deze strategie nog geen plaats. Het team dient te besluiten of ze terugkomen.
Tot dat besluit er is zijn ze geen verplichting maar ook niet geschrapt.
Dit plan geldt voor alle MOZA-backendservices en is zo geschreven dat een nieuwe service deze vanaf het begin kan overnemen.
Het is een uitwerking van de teststrategie beschreven in het eerdere hoofdstuk.
Dit plan beschrijft hoe we testen: het mechanisme van elke laag, het gereedschap en de definition of done.
We hanteren een testpiramide met contracttests als de naad tussen services, plus een set overkoepelende kwaliteitschecks die naast de piramide staan in plaats van erin. Elke laag en check beantwoordt één vraag. Een falende test moet direct naar de oorzaak wijzen.
Functionele lagen (de piramide):
mvn test).@QuarkusTest, REST Assured, Testcontainers (PostgreSQL), WireMock voor externe services.mvn verify).Functionele en acceptatietests zijn geen vijfde laag. Ze draaien in de componentlaag, met dezelfde tooling en dezelfde frequentie.
Alleen de reikwijdte en het lezerspubliek verschillen; zie Functionele en acceptatietests.
Kwaliteits- / niet-functionele checks (overkoepelend; geen piramidelagen, met een eigen frequentie):
@FuzzTest, ClusterFuzzLite.Robuustheid (timeouts, retries, circuit breaker) is geen aparte check. Dat wordt getest binnen de componentlaag met WireMock-fault-injection.
De regel voor waar een test thuishoort plus de redenering achter de vorm van dit model staan in Het testmodel van de teststrategie.
Kort beschreven, het mechanisme bepaalt de laag.
Logica uit een lagere laag wordt niet opnieuw in een hogere laag getest.
De unitlaag is de basis van de piramide en de enige laag die snel genoeg is om bij elke compilatie te draaien.
***Wat is een unittest hier?***
Een unittest is plain JUnit 5. Dat houdt in: geen @QuarkusTest, geen database, geen netwerk, geen bestandssysteem, en hij draait in milliseconden.
Alles wat een container nodig heeft is per definitie componentlaag, ongeacht in welk pakket het staat.
Dat betekent niet dat elke @QuarkusTest verkeerd is.
Sommige dingen bestaan alleen mét container. Bijv. CDI-interceptors, fault-tolerance-annotaties, transactiegrenzen en die horen daar thuis.
De regel is dat een klasse die géén container nodig heeft er ook geen gebruikt.
***Wat wel en niet een unittest krijgt:***
Wél, omdat er gedrag met vertakkingen in zit:
Niet, omdat er niets te falsifiëren valt:
***Ontwerpeisen die de unitlaag mogelijk maken:***
Deze staan hier omdat het staande eisen zijn voor iedere service, ook een service die nog niet bestaat.
Testbaarheid is een ontwerpeigenschap die je niet achteraf aan een suite kunt toevoegen.
Entiteit.persist(), Entiteit.deleteAll()) zijn niet te vervangen zonder container.
Clock.
Instant.now(), LocalDate.now() of System.currentTimeMillis() in productiecode.
Clock is tijdsafhankelijke logica alleen te testen door te wachten of door data relatief aan "nu" op te bouwen.
Test doubles
Determinisme
Thread.sleep en geen wachten op echte tijd; stuur de Clock.UUID.randomUUID() in asserties.
HashMap/HashSet, of op de volgorde waarin de database rijen teruggeeft.-Dsurefire.runOrder=random.Structuur, naamgeving en asserties
verlooptNaVervaltijd_geeftGeenToegang, niet testValidate2.verify(...) is voorbehouden aan de gevallen waarin de interactie zelf het contract is.
Code coverage
check zit in de build en laat de PR falen onder de drempel.
pitest-maven) draaien 's nachts op main, niet per PR.
Definition of done voor de unitlaag
Nieuwe of gewijzigde logica heeft unittests voor het happy path én voor elke vertakking die een ander resultaat oplevert. Nieuwe klassen voldoen aan de ontwerpeisen hierboven. Constructor-injectie en een geïnjecteerde Clock als er tijd in het spel is voordat ze gemerged worden, niet erna.
***Wat is een componenttest hier?***
Een componenttest start deze service en verder niets en stuurt hem over HTTP aan via zijn eigen API.
Alles buiten het proces wordt op de transportgrens gestubd; de database is echt.
De eenheid onder test is de deploybare eenheid.
Daarbinnen is alles echt: routing, filters en interceptors, JSON-serialisatie, bean-validatie, transactiegrenzen, persistentie, foutafhandeling.
Daarbuiten is alles gestubd maar gestubd als een proces op een socket, niet als Java-interface.
@QuarkusTest op zichzelf maakt nog geen componenttest. Een @QuarkusTest die een service injecteert en daar een methode op aanroept is geen componenttest maar een unittest die de prijs van een container/componenttest betaalt.
Een componenttest komt binnen via de API: REST Assured, één testklasse per controller (*ControllerIntegrationTest).
***Wat wel en niet een componenttest krijgt:***
Wél:
application/problem+json-body, centraal geassert (StandardErrorResponsesTest) en per endpoint waar de mapping specifiek is.
Niet:
Plaatsingstoets: Blijft de test betekenisvol als je de container weghaalt? Dan hoort hij in de unitlaag.
Stubben
Stub op de transportgrens, mock nooit de gegenereerde client.
Een componenttest die @InjectMock op een gegenereerde REST-client-interface zet slaat alles over wat de componentlaag hoort te bewijzen. De HTTP-call zelf, de serialisatie van request en response, de headers, de foutafhandeling op statuscodes, de fault-tolerance-annotaties én de vraag of we de spec van de ander correct interpreteren.
Wat overblijft is een unittest met een trage applicatiestart, het slechtste van twee lagen.
Stub externe services daarom op HTTP-niveau met WireMock (quarkus-wiremock, mappings in src/test/resources/mappings/).
Dat is bovendien de voorwaarde om pacts te kunnen genereren. Een gemockte interface produceert geen contract.
Stub ook de unhappy paden: timeouts, 5xx, misvormde bodies. Daar worden de fault-tolerance-annotaties getest (circuit breaker, retries), met WireMock-fault-injection (withFixedDelay, Fault.CONNECTION_RESET).
OpenAPI-conformiteit
Elk REST Assured-request/response wordt gevalideerd tegen het live OpenAPI-document via het Atlassian swagger-request-validator-filter (basisklasse OpenApiValidationTest). Dit is verplicht voor nieuwe controllertests: erf van de basisklasse.
De waarde hangt af van hoe de spec tot stand komt, en is het grootst waar hij met de hand onderhouden wordt:
oasdiff.mp.openapi.scan.disable=true): dan kan de spec écht van de implementatie afwijken, terwijl consumers er hun client uit genereren. In dit geval is deze validatie de enige bewaker van het verschil tussen spec en implementatie" en dus een must-have.Database en migraties
migrate-at-start=false met schema-management.strategy=validate): dan is de migratie een deployrisico in plaats van een testdetail.Testdata, isolatie en jobs
deleteAll() in een @AfterEach is globale, muteerbare state. Die maakt de uitvoeringsvolgorde betekenisvol, maakt parallellisatie onmogelijk en verbergt tests die stilzwijgend op de restanten van een andere test leunen.
Clock. Wachten op een cron-trigger is traag en niet-deterministisch.RetentieSchedulerTest doet dit al met retentie.verwijderd en retentie.anomalie.Thread.sleep.De laag betaalbaar houden
Een componenttest kost ordes van grootte meer dan een unittest en is tevens de laag die bepaalt of de PR-build onder de 15 minuten blijft.
Twee dingen blazen hem op:
@QuarkusTest-klassen heen maar een klasse met een andere configuratie - een @TestProfile, een andere @QuarkusTestResource - dwingt een herstart af.
Wachttijden in productiecode zijn configureerbaar en staan onder %test op vrijwel nul.
Definition of done voor de componentlaag
Een nieuw of gewijzigd endpoint:
OpenApiValidationTest;***Wat is een contracttest hier?***
Een contracttest toetst een afspraak tussen twee partijen terwijl er maar één van beide draait.
Dat is wat onafhankelijk deployen mogelijk maakt. Zonder contracttests kun je alleen bewijzen dat provider en consumer nog op elkaar passen door ze samen te deployen.
Dat zou een e2e-test bij elke PR betekenen wat precies is wat we proberen te vermijden (doel 2 van de strategie).
Een contracttest is geen functionele test van een van beide kanten. Een contracttest assert vorm en afspraak: dit veld bestaat, met dit type, onder deze voorwaarde.
Of bijvoorbeeld een bedrag correct berekend wordt is een zaak van de unitlaag.
Er zijn twee complementaire vormen. Hou beide aan.
***Provider en eigen specificatie (OpenAPI-conformiteit)***
Via swagger-request-validator. Lint daarnaast het OpenAPI-document tegen de NL GOV API Design Rules 2.1.0 met Spectral + de Logius ADR-ruleset in de PR-pipeline. Zo worden schendingen van de designregels vóór de review gevangen.
***Consumer en provider (Pact)***
De naad tussen twee MOZA-services.
Volgorde van invoering
Een Pact Broker is niet de eerste stap. Zonder echte consumertests is er niets om te publiceren.
En een provider die zijn eigen pact schrijft en tegen zichzelf verifieert bewijst niets.
@InjectMock op de gegenereerde client kan geen pact opleveren.pact-jvm consumer JUnit 5) voor zijn client van de andere service.
PactProviderVerificationTest toetst elke interactie tegen de draaiende service met @State-methoden voor het klaarzetten van data.pact-broker-container of PactFlow) in plaats van pact-bestanden tussen repo's te kopiëren:
@PactBroker) en publiceert de verificatieresultaten (pact.verifier.publishResults=true - alleen vanuit CI, nooit lokaal).can-i-deploy: een service mag alleen deployen als de broker bevestigt dat al haar contracten geverifieerd zijn tegen wat daar draait.Afhankelijkheden buiten ons beheer
Voor externe partijen waar we de provider niet beheren en niet kunnen afstemmen (bijvoorbeeld NotifyNL): beheerde WireMock-stubs plus een periodieke live smoke-check. Die smoke-check vereist credentials in CI en hoort in de nachtelijke laag, niet in de PR-laag.
Is de andere kant een endpoint dat de beller aanlevert (een callback-URL)? Dan is dat primair een securityvraagstuk en geen contractvraagstuk - zie Security- en robuustheidstesten.
Breaking changes
De OpenAPI-specificatie is geversioneerd (/api/<service>/v1). Een diff van het OpenAPI-document in de PR-pipeline (bijv. oasdiff) signaleert breaking changes en laat de build falen tenzij het majorversienummer wordt opgehoogd.
Pact-verificatie is het vangnet dat bewijst dat geen feitelijke consumer breekt.
Definition of done voor de contractlaag
Een wijziging aan het request of de response van een endpoint met een bekende consumer is pas klaar als het consumer-pact is bijgewerkt en geverifieerd, en oasdiff geen ongeversioneerde breaking change meldt.
Functionele dekking van businessregels zit primair in de componentlaag.
***Wat is een acceptatietest hier?***
Mechanisch is het een componenttest: dezelfde @QuarkusTest, dezelfde PR-laag, dezelfde tooling.
Het verschil zit in reikwijdte en lezerspubliek. Een componenttest assert één endpoint.
Een acceptatietest volgt een voor de gebruiker zichtbare journey over meerdere endpoints en assert de uitkomst die de user story belooft.
Omdat "functioneel" een intentie beschrijft en geen mechanisme is dit geen aparte suite en krijgt het geen aparte looptijd.
Eén acceptatietest per story, niet per endpoint. De test hoort te lezen als het acceptatiecriterium: given/when/then-gestructureerde REST Assured-tests (of Karate als niet-ontwikkelaars ze moeten kunnen lezen/schrijven; kies éénmalig, teambreed).
Typische onderwerpen: een verificatieflow inclusief verloop, het gedrag van een retentie-/opschoningsjob, autorisatie- en scope-gebaseerde filtering, en varianten van een flow die wezenlijk andere paden volgen.
Exploratory testing
Getimeboxt en charter-gestuurd: leg vóór aanvang vast welk gebied je onderzoekt, welk risico en binnen welke tijd, en achteraf de bevindingen.
Dit is de enige bewust handmatige activiteit. Waar de pipeline per PR een eigen omgeving deployt, doe je die sessie op een wijziging in plaats van pas per release op een gedeelde omgeving.
Bevindingen worden geautomatiseerde tests in de laag waar het defect zit.
Een validatiefout die je al verkennend vindt wordt een unittest, geen e2e-journey.
***Wat is een e2e-test hier?***
Twee of meer onafhankelijk gedeployde services, over een echt netwerk, tegen een gedeployde omgeving met niets in-process.
Hij bewijst wat geen enkele andere laag kan: dat los gebouwde artefacten samenwerken met de echte configuratie, de echte secretsinjectie, echt netwerk en een echte database.
Die definitie heeft gevolgen die de andere lagen niet hebben:
Welke journeys in aanmerking komen
Toelatingscriterium: welke storing vangt deze journey die geen enkele test van één losse service kan vangen? Is daar geen antwoord op, dan is het geen e2e-test.
Een component- of contracttest vangt dat goedkoper en wijst direct naar de oorzaak.
Een journey verdient zijn plek alleen door een grens over te gaan die de andere lagen niet zien:
Maximaal ~10 journeys. Happy path plus de één of twee foutpaden die over servicegrenzen heen gaan.
Journeys die nog niet gebouwd zijn horen pas in de suite als de code er is en niet als vooruitgeschoven skeleton.
Externe partijen blijven ook in e2e gestubd. Alleen de periodieke live smoke-check raakt de echte dienst.
Omgeving en testdata
moza-e2e-tests) blijft nuttig als plaats voor de testcode; docker compose of Kind is de terugvaloptie voor lokaal reproduceren en voor services die nog niet gedeployd worden.Flakiness
Nultolerantie: een flaky e2e-test gaat dezelfde dag in quarantaine en wordt binnen een sprint gerepareerd of verwijderd.
Failures melden zich in het teamkanaal met de logs van alle containers erbij.
Geen directe retries! Retries maskeren precies waar deze laag voor bestaat.
Een retry op journeyniveau mag alleen met een vastgelegde reden en een issue erop.
Een generieke retry over de hele suite mag niet want die kan een echt intermitterend defect omzetten in een groene build.
Frequentie en definition of done
Draait 's nachts tegen main-images en als releasegate.
Draait niet per PR. Contracttests zijn het per-PR-alternatief voor e2e.
Een journey komt pas in de suite als hij na zichzelf opruimt en een eigenaar met naam heeft.
***Wat is een misbruikcase?***
Tooling-gedreven checks (fuzzing, DAST, dependency-scanning) vinden generieke problemen.
Ze vinden niet dat een endpoint precies doet wat het volgens de code hoort te doen, maar voor iemand die dat niet zou mogen.
Een misbruikcase is een test geschreven vanuit het doel van de aanvaller, en hij assert de weigering.
Een gewone test assert wat het systeem doet voor iemand die het gebruikt zoals bedoeld terwijl een misbruikcase assert wat het weigert voor iemand die dat niet doet.
Mechanisch is het een gewone componenttest maar wat verschilt is het perspectief, niet de tooling. Reken op één test per uitgaande call en per authenticatiepunt.
De vier categorieën die in elke MOZA-service terugkomen:
http://localhost, privé-IP-ranges en link-local adressen geweigerd worden.Overige checks
@FuzzTest in de normale suite, ClusterFuzzLite per PR, batchrun wekelijks.
pom.xml en herbeoordeel ze bij elke platformupgrade../mvnw verify koud kunnen draaien).zap-api-scan.py, aangestuurd door de OpenAPI-spec) 's nachts tegen de gedeployde omgeving.
Elke bevinding krijgt een regressietest
Een bevinding uit een scan, een fuzz-crash, een pentest of een externe melding krijgt een test die haar reproduceert vóór de fix, en die test blijft daarna staan met een verwijzing naar de bevinding.
Zonder die test houdt niets tegen dat dezelfde fout bij een latere herschrijving terugkomt en is er geen bewijs dat de fix het gemelde geval daadwerkelijk afdekt.
Passend bij de levensfase (development/POC, op weg naar productie).
***Wat is een performancetest hier?***
Een performancetest assert een drempel. Zonder drempel is regressie moeilijk aan te tonen.
Drie dingen liggen vast vóór het eerste script geschreven wordt:
Twee modi
k6 of Gatling met een scenario per kritiek endpoint:
Waar het scenario rekening mee moet houden
Performancebudgetten staan in de repo naast de scripts, geversioneerd als code. Een overschreden budget is een buildfailure, geen aantekening. Is het nieuwe getal het bedoelde getal, dan wijzigt het budget in dezelfde PR als de code, bewust en zichtbaar.
Per pull request (verplichte checks, doel < 15 min doorlooptijd):
mvn -B verify) met Testcontainers PostgreSQLoasdiff breaking-change-check op het OpenAPI-documentLet op de fasebinding: de pipeline moet een Maven-fase draaien die álle gates bereikt.
Stopt de build op package, dan draaien de failsafe-ITs niet en wordt een aan verify gebonden coverage-gate nooit geëvalueerd.
's Nachts op main: mutatietesten (PIT), e2e-suite, ZAP API-scan, smoke-perf.
Wekelijks: 1 u batch-fuzzing.
Release: can-i-deploy tegen de doelomgeving, volledige e2e, loadtest als de wijziging daarom vraagt.
Pipeline-principes: testresultaten (Surefire-/Failsafe-XML, JaCoCo, Pact, k6) worden als CI-artifacts gepubliceerd.
Een rode nachtelijke build wordt de volgende ochtend getriageerd en kan releases blokkeren.
Tests in quarantaine worden als issue gevolgd met een eigenaar en een deadline.
Zie Deployment voor de omgevingen en de gebruikte CI/CD-tooling.
-Dgroups=unit) zodat een ontwikkelaar haar in seconden draait en PIT er alleen op kan mikken; verify draait alles.
@QuarkusTest bij zet (uitgangspunt 1).
*IT en failsafe zijn gereserveerd voor tests tegen het gebouwde containerimage (entrypoint, JVM-flags, non-root-gebruiker, health-endpoints) en bestaan pas zodra die er zijn.Clock voor tijdsafhankelijke logica.%test op vrijwel nul.
-Dsurefire.runOrder=random.