Een team voert een nachtelijke export uit die perfect werkt voor 999 gebruikers in de staging-omgeving, maar precies bij 1.000 gebruikers in productie een time-out geeft. Niemand heeft “1.000” getypt in de specificaties. Er is geen formulier om te valideren, geen leeftijdsgroep om te controleren. De grens is er wel degelijk, maar de testsuite heeft deze nooit aangeraakt.
Dat is de lacune die de meeste testprotocollen openlaten. Boundary value analysis (BVA) wordt vaak beschreven als een techniek voor het testen van invoervelden aan de uitersten: leeftijd 17, 18, 19 en 64, 65, 66. Dat werkt voor het formulier, maar mist al het andere. In de praktijk bevinden grenzen zich in cookiegroottes, cron-schema’s, connection pools en batch-runtimes, en geen van deze staat ergens opgeschreven waar een tester zou zoeken. Voor oprichters en productleads zijn de zakelijke gevolgen van het missen hiervan duidelijk: een grens die in productie overschreden wordt, leidt tot een support-brandje, klantverloop en een vertraagde roadmap. Dit is precies waar een systematische functionele test zichzelf terugverdient.
Dit artikel brengt de vijf plekken in kaart waar grenzen zich in een opgeleverd product verschuilen, en wat u bij elk daarvan moet testen.
De grens die niemand opschreef
Boundary value analysis bij softwaretesten is een testontwerptechniek die zich richt op de waarden op de randen van het systeemgedrag: het punt waar de ene regel eindigt en de andere begint. De tekstboekversie behandelt invoerbereiken. De nuttige versie behandelt al het andere.
De meeste concurrenten stoppen bij het invoerformulier omdat de grens daar gemakkelijk te zien is. Een veld zegt “18 tot 65”. Test 17, 18, 65, 66. Klaar. Echte producten hebben honderden grenzen die nooit in de specificaties worden genoemd: de grootte die een sessie-cookie kan bereiken voordat de browser deze verwijdert, het aantal rijen dat een Lambda kan verwerken voordat de runtime stopt, het exacte milliseconde-moment waarop een rate-limit venster reset. Elk van deze is een grens. Geen van hen ziet eruit als een grens totdat ze breken.
Het perspectief dat de moeite waard is om in de rest van dit artikel mee te nemen: boundary value analysis is een discipline voor het ontdekken van grenzen, geen oefening in het vervangen van waarden. De taak van de tester is om te vinden waar het systeem van gedrag verandert en vervolgens elk punt te toetsen.
Waarom off-by-one bugs nog steeds in productie komen
Off-by-one bugs komen nog steeds in productie omdat vereistendocumenten alleen de ideale invoer beschrijven, testers testen wat er geschreven staat, en impliciete grenzen pas onder belasting zichtbaar worden. Het Consortium for Information & Software Quality rapporteerde de kosten van slechte softwarekwaliteit in de Verenigde Staten op $2,41 biljoen in zijn meest recente analyse, en een aanzienlijk deel daarvan bestaat uit grenzen die niemand de moeite nam om te documenteren.
Drie patronen veroorzaken dit:
- De limiet van 1.000 rijen per batch staat in de timeout-configuratie van de job runner, niet in de PRD
- Als niemand schrijft “cookiegrootte moet onder 4 KB blijven”, voegt niemand daar een test voor toe
- Expliciete grenzen worden tijdens de ontwikkeling geautomatiseerd in unit tests; impliciete grenzen worden om 02.00 uur ‘s nachts ontdekt door gebruikers
De 2025 Annual Outage Analysis van het Uptime Institute rapporteert dat 85% van de storingen door menselijke fouten terug te voeren is op personeel dat procedures niet volgt of op gebreken in de procedures zelf. Het ontbreken van tests voor ongedocumenteerde grenzen valt volledig in de tweede categorie. Stop met vragen: “Wat is het bereik in de specificaties?” Begin met vragen: “Waar verandert dit systeem van gedrag?”
Boundary value analysis versus edge case testing
De twee termen worden door elkaar gebruikt, wat een nuttig onderscheid verbloemt. BVA is numeriek en precies: het bevindt zich op een meetbare drempel, zoals rij 999 versus 1.000 of byte 4.095 versus 4.096. Edge case testing is breder en omvat vreemde toestanden en ongebruikelijke combinaties die misschien geen duidelijke numerieke grens hebben, zoals een apparaat dat offline is, een gebruiker zonder rechten of een beschadigde upload.
Elke grens is een edge case. Niet elke edge case is een grens. Als u een getal kunt plakken op waar het gedrag verandert, is BVA het juiste instrument. Als de verandering kwalitatief is in plaats van kwantitatief, is edge case testing geschikter; dit dekt een groter deel van het gebied “waar de vereisten eindigen en de realiteit begint”.
Vijf plekken waar grenzen zich verschuilen
Hieronder volgen de vijf categorieën waar grenzen leven die niet in de documentatie staan. Elke categorie begint met een bugpatroon, benoemt de ongedocumenteerde grens en geeft de testgevallen die deze aan het licht brengen. Een concreet voorbeeld van boundary value analysis vindt u in elke categorie in plaats van in slechts één enkel veld.
Infrastructuurlimieten die niemand documenteerde
Het sessie-cookie van een gebruiker groeit na het toevoegen van een vijfde OAuth-provider tot voorbij 4 KB, waardoor elk volgend verzoek de sessie stilletjes laat vallen. Een URL met 50 filterparameters overschrijdt de 8 KB header-limiet van een downstream proxy en de API geeft een 400-fout zonder gelogde reden. Geen van deze limieten staat in de productspecificaties. Beide staan in een configuratiebestand.
De verborgen grenzen op dit niveau zijn onder andere:
- Cookiegrootte, in de meeste browsers begrensd tot ongeveer 4 KB per cookie
- HTTP-headergrootte, doorgaans 8 tot 16 KB afhankelijk van de server
- URL-lengte, met praktische CDN- en browserlimieten rond 2 KB
- Request body-grootte, standaard 1 MB in nginx
- Packet MTU op 1.500 bytes op standaard Ethernet
Testgevallen die de moeite waard zijn om toe te voegen: een payload net onder en net boven elke limiet, een sessie-cookie dat de 4 KB nadert, een URL met voldoende query-parameters om de 2 KB te overschrijden, een upload die qua grootte net de body-limiet van de proxy overschrijdt. Controleer de configuratie, niet de specificaties. Wanneer deze op het API-niveau verschijnen, is API testing de methode om ze te vangen.
Tijd- en kalandervallen
Tijd is de categorie die aannames het hardst afstraft. Een abonnement dat op de 31e wordt gefactureerd, brengt in maart dubbele kosten in rekening en slaat april over. Een cron-job die voor 02.30 uur is gepland, draait tweemaal tijdens de omschakeling naar de wintertijd en helemaal niet in de lente. Een timestamp-veld geschreven als int32 zal op 19 januari 2038 om 03:14:07 UTC overstromen; het jaar 2038-probleem is een grens met een datum.
De temporele grenzen om te testen zijn onder meer overgangen aan het einde van de maand (28, 29, 30 en 31 dagen), schrikkeldagen, zomertijd/wintertijd-overgangen in beide richtingen, ISO-week 53-omloop en factuurdata die verschuiven wanneer een maand minder dagen heeft dan de factuurdatum. Zet een abonnement op 31 januari, zet de klok door naar 28 februari en kijk wat de facturatie-engine doet. Voer een geplande taak uit over een zomertijd-grens in een tijdzone die zomertijd volgt. Boek een hotel met inchecken op 28 februari 2028 en uitchecken op 1 maart.
Datatype-plafonds
Een teller die is opgeslagen als INT bereikt 2.147.483.647, waarna de volgende verhoging verspringt naar een negatief getal of een foutmelding geeft. Een valutaveld opgeslagen als float accumuleert afrondingsfouten en een factuur van $100,00 wordt verrekend als $99,9999998. Een gebruikersnaamkolom die tussen releases is verbreed van VARCHAR(50) naar VARCHAR(100), kapt stilletjes rijen af die zijn gemigreerd vanuit het oude schema.
De grenzen die verborgen zitten in het schema:
Een tellerkolom (zoals weergaven, likes, transacties)
Rond de 2,14 miljard
De teller springt plotseling naar een negatief getal of geeft een foutmelding
Elk groot getal dat naar een JavaScript-frontend wordt verzonden
Rond de 9 quadriljoen
Het getal verliest precisie: de laatste cijfers veranderen ongemerkt tijdens het transport
Een prijs- of valutaveld dat is opgeslagen als een decimale benadering
Ongeveer 7 cijfers nauwkeurigheid
$100,00 komt terug als $99,9999998 en het grootboek stopt met sluitend te zijn
Een tekstkolom met een vaste breedte (naam, e-mail, beschrijving)
De breedte waarop de kolom is ingesteld
Bij een database-upgrade worden langere waarden zonder waarschuwing afgekapt
Automatisch gegenereerde ID’s vanuit een zwakke willekeurige bron
Eerder dan de wiskunde belooft
Twee records krijgen hetzelfde ID en de een overschrijft de ander
Lees het schema voordat u het testplan schrijft. Deze grenzen zijn onzichtbaar in de UI, maar overduidelijk in de database.
Pagination- en batchlimieten
Pagination-grenzen bevinden zich op de eerste pagina, de laatste pagina en de overgang tussen pagina’s wanneer het totaal een exact veelvoud is van de paginagrootte. Batchgrenzen bevinden zich op elke runtime-limiet die de executor afdwingt, of het nu de 15 minuten van Lambda zijn, de 60 minuten van Cloud Run of de cronjob van 5 minuten die het SRE-team twee jaar geleden heeft ingesteld.
Het klassieke patroon: een export verwerkt 999 rijen in staging en krijgt een time-out bij 1.000 in productie omdat de werkelijke grens de runtime-limiet is, niet het aantal rijen. Een ander voorbeeld: een lijstweergave toont 10 items per pagina en pagina 10 wordt leeg weergegeven omdat de cursorlogica offset = pagina × limiet is, waarbij paginering in de URL bij 1 begint en in de query bij 0.
Gevallen om toe te voegen: laad exact pagina_grootte × N records en vraag pagina N en N+1 aan. Vraag pagina 0 en pagina -1 aan. Voer de batchtaak uit op een dataset die één rij boven de waargenomen time-outdrempel ligt. Exporteer een tabel met exact de rijlimiet van de tool; Google Sheets heeft een limiet van 10 miljoen cellen, Excel van 1.048.576 rijen.
Concurrency-drempels
De 5e gelijktijdige gebruiker op een account met sessielimieten triggert een verwijdering van sessie 1, maar het verwijderingsslot veroorzaakt een deadlock met het inlogproces. De database-connection pool staat op 20 en de 21e query blokkeert stilletjes in plaats van een fout te geven. Een rate-limiter staat 100 verzoeken per minuut toe; het 101e verzoek in een burst geeft een 429-fout, maar de teller reset één milliseconde in het volgende venster en dezelfde client piekt direct opnieuw.
Concurrency-grenzen omvatten de limiet voor het aantal seats in een abonnement, het plafond van de database-connection pool, de grootte van de thread pool, de grens van het rate-limit-venster (vast versus glijdend), drempels voor lock contention en de wachtrijdiepte waarbij consumenten achterblijven bij producenten. Test dit door één sessie meer te openen dan het abonnement toestaat en het gedrag bij verwijdering te observeren. Voer pool_grootte + 1 gelijktijdige queries uit en meet blokkering versus foutmeldingen. Verstuur N verzoeken op de grens van het vaste venster van de rate-limit en verstuur vervolgens N meer één milliseconde na de reset.
Dit zijn de grenzen die verborgen blijven totdat het verkeer schaalt. De 2025 ITIC/Calyptix SMB Downtime Survey toonde aan dat 8% van de kleine en middelgrote bedrijven nu downtime-kosten meldt die de $25.000 per uur overschrijden, en een concurrency-plafond dat om 3 uur ‘s nachts wordt bereikt, is een van de snelste manieren om dat bedrag te halen.
Grenzen vinden die niet in de specificaties staan
Ontdekking is waardevoller dan functievervanging. Vier vragen werken voor elke functie die wordt getest:
- Waarvan is deze functie afhankelijk? Cookies, headers, URL’s, request bodies, timers, databasekolommen, connection pools. Elke afhankelijkheid heeft een limiet.
- Wat is de limiet van elke afhankelijkheid? Lees het configuratiebestand, het schema, de SLA of de quota-pagina van de cloudprovider. Als de limiet nergens staat geschreven, voer dan een loadtest uit om deze te vinden.
- Wat gebeurt er exact op die limiet, één eronder en één erboven? Dit is de klassieke BVA-methode, toegepast op infrastructuur in plaats van op een formulierveld.
- Wat gebeurt er wanneer de limiet verandert? Een schemamigratie, een upgrade van een abonnement, een aanpassing in de configuratie. Grenzen verschuiven. Tests moeten met hen meebewegen.
De grens is reëel, of iemand hem nu heeft opgeschreven of niet. Het is de taak van de tester om deze te vinden voordat de gebruiker dat doet.
Waar BVA in uw testontwerp past
Boundary value analysis (BVA) ontdekt waar een systeem van gedrag verandert. Equivalentiepartitionering vertelt u welke waarden tussen grenzen u veilig kunt overslaan. Samen comprimeren ze een enorme invoerruimte tot een kleine, zeer informatieve testset die de fouten vangt die de moeite waard zijn om te vangen.
Twee buren in dezelfde toolkit maken BVA scherper. Equivalentiepartitionering groepeert waarden die zich op dezelfde manier gedragen en vertelt u welke waarden tussen grenzen u veilig kunt overslaan. Negatief testen genereert de slechte invoer die niet door de vereisten is toegestaan, waardoor fouten worden gevangen waar BVA niet op jaagt. Samen comprimeren ze een enorme invoerruimte tot een kleine, zeer informatieve testset die de fouten vangt die de moeite waard zijn om te vangen.
Breng de grens in kaart voordat uw gebruikers dat doen
De tester die verborgen grenzen beheert, test geen waarden. Zij brengen de plaatsen in kaart waar het systeem van gedrag verandert en onderzoeken vervolgens elk punt. Het document met vereisten is het startpunt van de grenswaardeanalyse, niet de hele kaart.
Als uw laatste ‘off-by-one’-fout door de testsuite is geglipt, is de oplossing niet meer testgevallen, maar een systematische controle van de grenzen die niemand heeft opgeschreven. Neem contact met ons op en we bekijken waar ze in uw product verborgen zouden kunnen zijn.
Veelgestelde vragen
Welke grenzen moet ik testen naast invoerbereiken?
Test de grenzen die niet in het vereistendocument staan: de grootte van cookies en headers, URL-lengte, limieten voor request body, MTU, overgangen bij zomertijd/wintertijd en aan het einde van de maand, precisielimieten voor int32 en float, VARCHAR-breedte bij verschillende schemaversies, paginering bij de eerste/laatste/exacte pagina, maximale batch-runtime, plafonds voor connection pools, de randen van rate-limit-vensters en limieten voor het aantal gebruikerslicenties. Dit is waar off-by-one-bugs vaak in de productieomgeving belanden.
Hoe vindt u grenzen die niet in de vereisten staan?
Stel bij elke functie vier vragen: waar is deze van afhankelijk, wat is de limiet van elke afhankelijkheid, wat gebeurt er op en rond die limiet, en wat gebeurt er als de limiet verandert? Lees het configuratiebestand, het schema en de pagina met quota van de cloudprovider. Als een limiet niet is gedocumenteerd, zal een loadtest deze aan het licht brengen.
Wat is het verschil tussen grenswaardeanalyse en edge-case-testen?
Grenswaardeanalyse is numeriek en precies; het bevindt zich op een meetbare drempel, zoals rij 999 versus 1.000. Edge-case-testen is breder en omvat vreemde toestanden of ongebruikelijke combinaties die misschien geen duidelijke numerieke grens hebben, zoals een offline apparaat of een gebruiker zonder rechten. Elke grenswaarde is een edge-case, maar niet elke edge-case is een grenswaarde.
Waarom komen off-by-one-bugs in 2026 nog steeds in de software voor?
Vereistendocumenten beschrijven de ideale invoer, testers testen wat er is vastgelegd en impliciete grenzen komen pas naar boven onder belasting. Expliciete grenzen worden tijdens de ontwikkeling geautomatiseerd. Impliciete grenzen, zoals een runtime-limiet van Lambda of de maximale grootte van een cookie, worden door gebruikers om 02:00 uur ’s nachts ontdekt, omdat niemand een test schreef voor een limiet die niemand had gedocumenteerd.
Ontdek hoe QAwerk het aantal bugrapportages na de lancering voor een app voor sms-berichten met 65% verminderde door systematisch de fouten op te sporen die pas op schaal zichtbaar worden.