Datakwaliteit & Testen

De zes klassieke datakwaliteitsdimensies als denkkader, anomalie-gebaseerd testen naast vaste regels, de testpiramide toegepast op data, quarantainepatronen bij falende tests, en Great Expectations/Soda als aanvulling op dbt-tests.

11 min leestijd Gemiddeld Bijgewerkt: 2026-08-18 Governance

Introductie

Testing is in dit handbook al concreet behandeld — de teststrategie per medallion-laag in Medallion Architecture, generic versus singular tests in dbt Fundamentals, testdata-strategie in CI/CD in CI/CD for Data Engineering. Dit hoofdstuk herhaalt die dbt-mechaniek niet, maar behandelt datakwaliteit als vakgebied op zich: het denkkader waarmee je "kwaliteit" concreet maakt, testvormen die verder gaan dan vaste regels als not_null, en wat er operationeel gebeurt wanneer een test daadwerkelijk faalt.

De Zes Datakwaliteitsdimensies

Voordat je kunt testen, moet je kunnen benoemen wát je precies test. De industrie heeft zich grotendeels rond zes dimensies geschaard, en elke concrete test die je schrijft — of het nu een dbt-test is of iets geavanceerders — valt in de kern onder een van deze:

Accuraatheid — komt de waarde overeen met de werkelijkheid? Een adres dat bestaat maar niet klopt met waar de klant daadwerkelijk woont, is inaccuraat maar niet per se incompleet of ongeldig.

Volledigheid — ontbreken er waarden die er wel zouden moeten zijn? Dit is wat not_null-tests uit dbt Fundamentals meten, maar volledigheid gaat breder: ontbreken er hele rijen (een dag zonder orders terwijl de winkel open was)?

Consistentie — komt dezelfde entiteit overeen tussen verschillende systemen of tabellen? Een klant met verschillende geboortedatums in het CRM en het factuursysteem schendt consistentie, ook al is elke waarde op zich mogelijk geldig.

Tijdigheid — is de data vers genoeg voor het gebruiksdoel? Dit is precies waar freshness-checks uit Medallion Architecture op inspelen, en waar Monitoring & Observability verder op ingaat.

Validiteit — voldoet een waarde aan het verwachte formaat of bereik? Een e-mailadres zonder @, een leeftijd van -5 — dit is het domein van accepted_values- en range-tests.

Uniciteit — komt een entiteit precies één keer voor waar dat verwacht wordt? Dit is wat unique-tests bewaken, en de reden dat deduplicatie uit SQL for Data Engineers zo'n centrale plek inneemt.

Het nut van dit denkkader: als een stakeholder zegt "de data klopt niet", dwingt deze indeling je om te vragen welke dimensie precies faalt — want de oplossing voor een volledigheidsprobleem (ontbrekende rijen onderzoeken bij de bron) is compleet anders dan voor een consistentieprobleem (twee systemen synchroniseren).

Vaste Regels versus Anomalie-gebaseerd Testen

dbt's generic tests (not_null, unique, accepted_values) zijn deterministisch: een vaste, vooraf bekende regel die een rij wel of niet doorstaat. Dit werkt uitstekend voor validiteit en uniciteit, maar schiet tekort bij problemen die zich pas tonen als afwijking van een patroon, niet als schending van een vaste regel.

Stel: het aantal orders per dag ligt normaliter tussen 800 en 1200. Een dag met 3 orders schendt geen enkele not_null- of unique-regel — elke individuele rij is perfect geldig — maar er is overduidelijk iets mis met de ingestion. Dit is waar anomaliedetectie in beeld komt: tests die niet een vaste waarde controleren, maar of een metriek significant afwijkt van zijn historische verdeling.

-- Eenvoudige, zelfgebouwde anomaliecheck: vergelijk vandaag met een historisch gemiddelde
WITH daily_counts AS (
    SELECT order_date, COUNT(*) AS order_count
    FROM silver.orders
    WHERE order_date >= CURRENT_DATE - 30
    GROUP BY order_date
),
stats AS (
    SELECT AVG(order_count) AS avg_count, STDDEV(order_count) AS stddev_count
    FROM daily_counts
    WHERE order_date < CURRENT_DATE
)
SELECT d.order_date, d.order_count, s.avg_count
FROM daily_counts d, stats s
WHERE d.order_date = CURRENT_DATE
  AND ABS(d.order_count - s.avg_count) > 3 * s.stddev_count;   -- >3 standaarddeviaties afwijking

Packages als dbt_expectations (genoemd in dbt Fundamentals) bieden kant-en-klare varianten hiervan, maar het onderliggende principe blijft hetzelfde: een grens gebaseerd op historisch gedrag, niet op een hardgecodeerde waarde die iemand ooit heeft bedacht.

Genereer tests direct bij je model

Laat de dbt Generator een model met bijpassende schema.yml-tests voor je opzetten.

Open dbt Generator

De Testpiramide Toegepast op Data

Softwareontwikkeling kent de testpiramide: veel snelle, goedkope unit tests aan de basis, minder integratietests, een klein aantal trage end-to-end-tests aan de top. Hetzelfde principe vertaalt zich naar dataplatformen, met een net iets andere invulling per laag.

Unit tests op transformatielogica — test een SQL-functie of macro geïsoleerd, met kunstmatige, kleine input, zonder een heel warehouse te raken. dbt ondersteunt dit via unit tests op modelniveau: je geeft vaste input-rijen op en controleert de exacte output, vergelijkbaar met een unit test in reguliere software.

Integratietests op pipelines — controleer of een volledige bronze-naar-silver-naar-gold-keten correct doorloopt met een kleine, representatieve testset (de seeds/steekproef-afweging uit CI/CD for Data Engineering), zonder tegen volledige productievolumes te draaien.

Data tests op productiedata zelf — dit zijn de generic/singular dbt-tests uit dbt Fundamentals en de anomaliechecks hierboven, uitgevoerd tegen de daadwerkelijke, actuele data. Dit is de enige laag die niet vervangen kan worden door een kleinere testset, omdat het punt juist is de echte data te controleren.

De veelgemaakte fout is alle drie de lagen door elkaar te gebruiken: dure, volledige data-tests bij elke code-commit draaien (traag, kostbaar) in plaats van snelle unit tests voor logicafouten en data-tests specifiek voor wat alleen op echte data zichtbaar wordt.

Wat Gebeurt er als een Test Faalt? Quarantaine versus Blokkeren

Een test die faalt, roept een operationele vraag op die dbt's testframework zelf niet beantwoordt: moet de hele pipeline stoppen, of moet alleen de foute data apart gezet worden terwijl de rest doorgaat? Twee patronen, met een directe parallel in de expect/expect_or_drop/expect_or_fail-niveaus van Delta Live Tables uit Databricks.

Hard falen (blokkeren) — de pipeline stopt volledig zodra een kritieke test faalt, en niets stroomafwaarts wordt bijgewerkt met mogelijk onbetrouwbare data. Geschikt voor tests op fundamentele garanties (een primary key die niet uniek is, een verplicht veld dat massaal ontbreekt) waar doorgaan erger is dan stilstaan.

Quarantaine (zachte afhandeling) — rijen die een test niet doorstaan, worden apart gezet in een quarantainetabel voor onderzoek, terwijl geldige rijen gewoon doorstromen naar de volgende laag. Geschikt voor problemen die een klein deel van de data raken en waar de rest van de pipeline niet hoeft te wachten op een handmatig onderzoek van een randgeval.

-- Quarantainepatroon: scheid geldige van ongeldige rijen, blokkeer niets
CREATE OR REPLACE TABLE silver.orders AS
SELECT * FROM staging.orders_clean
WHERE order_id IS NOT NULL AND amount >= 0;

CREATE OR REPLACE TABLE silver.orders_quarantine AS
SELECT *, CURRENT_TIMESTAMP() AS quarantined_at
FROM staging.orders_clean
WHERE order_id IS NULL OR amount < 0;

De keuze tussen deze twee is zelden platformbreed — net als bij de ETL/ELT-keuze uit ETL vs ELT is dit een beslissing per test, afhankelijk van hoe kritiek en hoe wijdverspreid het risico van doorgaan is.

Voorbij dbt: Great Expectations en Soda

dbt's ingebouwde tests dekken veel, maar gespecialiseerde datakwaliteitstools bieden functionaliteit die dbt zelf niet heeft. Great Expectations werkt met "expectation suites" — declaratieve, herbruikbare verzamelingen verwachtingen die zowel binnen als buiten een dbt-project kunnen draaien, met uitgebreide, automatisch gegenereerde datakwaliteitsrapporten (data docs) als bijproduct:

import great_expectations as gx

context = gx.get_context()
validator = context.sources.pandas_default.read_csv("orders.csv")

validator.expect_column_values_to_not_be_null("order_id")
validator.expect_column_values_to_be_between("amount", min_value=0, max_value=100000)
validator.expect_table_row_count_to_be_between(min_value=500, max_value=5000)

Soda Core volgt een vergelijkbare filosofie maar met een lichtgewicht, YAML-gebaseerde syntax (SodaCL), specifiek ontworpen om eenvoudig in bestaande pipelines te integreren zonder een aparte Python-omgeving op te tuigen:

# checks.yml
checks for silver.orders:
  - row_count between 500 and 5000
  - missing_count(order_id) = 0
  - duplicate_count(order_id) = 0

De praktische vuistregel: gebruik dbt's ingebouwde tests als standaard voor alles wat al binnen je dbt-project leeft — het is al aanwezig, geen extra tool nodig. Grijp naar Great Expectations of Soda specifiek wanneer je datakwaliteit wilt bewaken op plekken die dbt niet bereikt (ruwe bestanden vóór ze het warehouse bereiken, of een gedeeld kwaliteitsraamwerk dat meerdere teams met verschillende transformatietools moeten kunnen gebruiken).

Contract Tests: Testen op de Grens Tussen Teams

Modern Data Platform Architecture introduceerde het data contract als afspraak tussen een producerend en consumerend team, vastgelegd als YAML. Het contract wordt pas waardevol zodra het ook daadwerkelijk getest wordt — niet als documentatie die niemand meer leest, maar als een CI-check die faalt zodra de werkelijkheid van het contract afwijkt:

# test_contract_orders.py — draait in CI van het producerende team
import yaml

def test_orders_schema_matches_contract():
    contract = yaml.safe_load(open("contracts/orders.yml"))
    actual_columns = get_live_schema("production.orders")  # query tegen de bron

    contract_columns = {c["name"]: c["type"] for c in contract["contract"]["schema"]}
    for name, expected_type in contract_columns.items():
        assert name in actual_columns, f"Contractkolom '{name}' ontbreekt in de bron"
        assert actual_columns[name] == expected_type, f"'{name}' heeft type {actual_columns[name]}, contract verwacht {expected_type}"

Dit draait in de CI-pijplijn van het producerende team (het productteam uit het Flowmetric-voorbeeld), zodat een schemawijziging die het contract breekt al wordt gevangen vóórdat die code naar productie gaat — niet pas wanneer het datateam een week later een mysterieus falende dbt-run ontdekt. Dit is het verschil tussen een data contract als intentie en een data contract als afgedwongen garantie, en het is precies de reden dat contract-tests in bredere organisaties steeds vaker als verplichte CI-stap voor bronsystemen worden ingericht.

Testdekking Meten en Bewaken

Een vraag die zelden gesteld wordt maar wel zou moeten: hoeveel van je kritieke modellen hebben eigenlijk tests? Zonder dit te meten groeit een dbt-project vaak organisch met tests op de eerste paar modellen, gevolgd door tientallen nieuwe modellen zonder enige test omdat niemand het structureel bewaakt. dbt's eigen manifest (dezelfde manifest.json die Slim CI uit CI/CD for Data Engineering gebruikt) bevat genoeg informatie om dit te berekenen:

import json

manifest = json.load(open("target/manifest.json"))
models = {k: v for k, v in manifest["nodes"].items() if v["resource_type"] == "model"}
tested_models = {m for t in manifest["nodes"].values() if t["resource_type"] == "test"
                  for m in t.get("depends_on", {}).get("nodes", []) if m in models}

coverage = len(tested_models) / len(models) * 100
print(f"Testdekking: {coverage:.1f}% ({len(tested_models)}/{len(models)} modellen)")

Net als codedekking in reguliere software is 100% zelden het doel — sommige modellen zijn triviaal genoeg dat een test weinig toevoegt — maar het zichtbaar maken van dekking, bijvoorbeeld als vast onderdeel van een sprint-review of kwartaalrapportage, voorkomt dat kritieke gold-modellen jarenlang ongetest blijven simpelweg omdat niemand het ooit expliciet controleerde.

Best Practices

  • Koppel elke test expliciet aan een datakwaliteitsdimensie in je documentatie — het maakt duidelijk wélk probleem een falende test signaleert, niet alleen dát er iets faalt.
  • Gebruik anomaliedetectie naast vaste regels, niet in plaats daarvan — vaste regels vangen bekende schendingen, anomaliedetectie vangt onbekende patroonverschuivingen.
  • Kies quarantaine als standaard, hard falen als uitzondering — reserveer blokkerende tests voor garanties waarvan schending de hele downstream-keten onbetrouwbaar maakt.
  • Gebruik unit tests voor logicafouten, data-tests voor datafouten — laat data-tests niet de taak van unit tests overnemen, dat maakt CI onnodig traag.
  • Reserveer Great Expectations/Soda voor wat dbt niet bereikt, niet als vervanging van dbt's ingebouwde tests waar die al volstaan.

Veelgemaakte Fouten

  • Alleen op vaste regels vertrouwen, waardoor een geleidelijke, patroonmatige verslechtering (een langzaam dalend ordervolume) onopgemerkt blijft omdat geen enkele individuele rij een regel schendt.
  • Elke falende test hard laten blokkeren, ook voor kleine, randgevallen — wat pipelines fragiel en operationeel vermoeiend maakt.
  • Quarantainetabellen aanmaken zonder ooit iemand te laten kijken wat erin staat — quarantaine zonder opvolging is alleen uitstel van hetzelfde probleem.
  • Dure data-tests bij elke commit draaien in plaats van ze te reserveren voor waar ze daadwerkelijk nodig zijn, wat CI onnodig traag maakt.
  • Een aparte tool (Great Expectations/Soda) invoeren zonder concrete leemte die dbt's eigen tests niet al dekken — onnodige toolingcomplexiteit.

Performance Tips

  • Laat anomaliechecks draaien op vooraf geaggregeerde metrieken (dagelijkse tellingen, niet ruwe rijen), zodat de check zelf snel blijft ongeacht hoe groot de onderliggende tabel is.
  • Beperk quarantaine-checks tot de silver-laag, niet elke laag — bronze hoort sowieso ruw te blijven, gold bouwt al voort op reeds gevalideerde silver-data.
  • Cache historische statistieken voor anomaliedetectie (gemiddelde, standaarddeviatie) in plaats van ze bij elke run opnieuw over de volledige historie te berekenen.
  • Parallelliseer onafhankelijke tests in CI waar mogelijk, dezelfde overweging als bij Slim CI uit CI/CD for Data Engineering.

Interviewvragen

"Noem de zes datakwaliteitsdimensies en geef van elk een voorbeeld." Let op: accuraatheid, volledigheid, consistentie, tijdigheid, validiteit, uniciteit — met een concreet, onderscheidend voorbeeld per dimensie, niet alleen de namen opsommen.

"Wat is het verschil tussen een vaste-regel-test en anomaliedetectie?" Let op: vaste regels controleren een vooraf bekende grens (not_null, range); anomaliedetectie vergelijkt met een historisch patroon en signaleert onverwachte afwijkingen die geen enkele vaste regel zou vangen.

"Wanneer zou je een falende test laten blokkeren versus de foute rijen laten quarantaine?" Let op: blokkeren voor fundamentele garanties waar doorgaan de hele downstream-keten onbetrouwbaar maakt; quarantaine voor lokale, beperkte problemen waar de rest van de pipeline niet hoeft te wachten.

"Leg de testpiramide uit zoals toegepast op een dataplatform." Let op: unit tests op transformatielogica (snel, geïsoleerd), integratietests op pipelines met representatieve testdata, data-tests op de daadwerkelijke productiedata — elk met een andere snelheid/kostenafweging.

"Wanneer zou je Great Expectations of Soda gebruiken naast dbt's eigen tests?" Let op: specifiek voor datakwaliteitscontrole buiten het bereik van dbt (ruwe bestanden vóór het warehouse, gedeelde kwaliteitsraamwerken over meerdere tools heen) — niet als standaardvervanging van dbt-tests.

"Hoe zou je een data contract daadwerkelijk afdwingen, niet alleen documenteren?" Let op: een contract-test die in de CI-pijplijn van het producerende team draait en faalt zodra het werkelijke schema afwijkt van het contract — vóórdat de wijziging productie bereikt, niet pas wanneer een downstream-consument iets ziet breken.

"Hoe zou je meten of een dbt-project voldoende testdekking heeft?" Let op: het aantal modellen met minstens één gekoppelde test afzetten tegen het totale aantal modellen, afleidbaar uit manifest.json — en het besef dat 100% zelden het doel is, maar zichtbaarheid wel voorkomt dat kritieke modellen structureel ongetest blijven.

Relevante Documentatie

Samenvatting

De zes datakwaliteitsdimensies — accuraatheid, volledigheid, consistentie, tijdigheid, validiteit, uniciteit — geven taal aan wat "kwaliteit" concreet betekent, en elke test die je schrijft valt in de kern onder een ervan. Vaste regels (dbt's generic tests) vangen bekende schendingen; anomaliedetectie vangt patroonverschuivingen die geen enkele vaste regel zou signaleren. De testpiramide (unit, integratie, data) bepaalt welke laag welke snelheid en dekking verdient, en de keuze tussen hard blokkeren en quarantaine bepaalt wat er operationeel gebeurt zodra een test daadwerkelijk faalt. Great Expectations en Soda vullen aan waar dbt's eigen tests niet reiken. Met datakwaliteit als denkkader op zak, behandelt het volgende hoofdstuk het bredere organisatorische raamwerk eromheen: Data Governance.

Hulp nodig bij het bouwen van een modern dataplatform?

DataPartner365 helpt organisaties met Microsoft Fabric, Snowflake, Databricks, dbt, Azure, data-architectuur en CI/CD.

Neem contact op met DataPartner365