dbt Tests: der Leitfaden zu Prüfungen der Datenqualität
dbt Tests sind automatische Zusicherungen an die Datenqualität, die gegen die Tabellen und Views laufen, die Ihre dbt-Modelle bauen. Sie beantworten eine einfache Frage: „Entsprechen die Daten in diesem Modell unseren Erwartungen?“ Ein bestandener Test heißt, dass die Annahme für die aktuellen Daten gilt; ein fehlgeschlagener Test legt ein Problem der Datenqualität offen, bevor es still die nachgelagerten Dashboards, die Merkmale für das maschinelle Lernen oder die Finanzberichte verdirbt.
dbt Tests sind Anweisungen im SQL SELECT, die Zeilen zurückgeben, wenn eine Zusicherung verletzt ist (null Zeilen = bestanden). Es gibt zwei Arten: generische Tests, die in schema.yml erklärt werden (vier eingebaute: not_null, unique, accepted_values, relationships), und singuläre Tests, also eigene Dateien in SQL im Verzeichnis tests/. Pakete aus der Community wie dbt_utils und dbt_expectations ergänzen über 50 weitere Testarten. Die Ergebnisse der Tests fließen in Plattformen für die Governance wie Dawiso, die daraus eine Sicht auf die Datenqualität über den ganzen Katalog hinweg bauen.
Was sind dbt Tests?
dbt bringt ein eingebautes Rahmenwerk für Tests mit, das keine zusätzliche Infrastruktur braucht: Tests sind schlicht Abfragen in SQL, die dbt übersetzt und gegen Ihr Warehouse ausführt. Jeder Test übersetzt zu einer SELECT-Anweisung, die Zeilen zurückgibt, wenn die Zusicherung verletzt ist. Gibt die Abfrage null Zeilen zurück, ist der Test bestanden. Dieser Weg ist aus mehreren Gründen stark:
- Die Tests laufen in Ihrem Warehouse, es braucht weder zusätzliche Rechenleistung noch zusätzliche Werkzeuge.
- Die Tests sind Code, sie liegen in der Versionskontrolle neben den Modellen, die sie schützen.
- Die Tests sind ausführbare Dokumentation, sie legen fest, wie „richtige Daten“ aussehen.
- Ein fehlgeschlagener Test kann die Pipelines für CI/CD anhalten und verhindern, dass schlechte Daten in die Produktion gelangen.
Ohne automatische Tests kommen Rückschritte in der Datenqualität auf die harte Tour ans Licht: Eine Analystin bemerkt eine Auffälligkeit, jemand aus dem Fachbereich stellt eine Zahl in Frage, oder ein Produkt zeigt den Kunden falsche Angaben. Bis das Problem auffällt, stehen die schlechten Daten womöglich seit Tagen oder Wochen in der Produktion. dbt Tests verschieben die Qualität nach vorn und fangen Probleme in der Schicht der Transformation ab, bevor sie die nachgelagerten Data Consumer treffen.
Generische Tests: die vier eingebauten Arten
Generische Tests sind parametrisierte Definitionen von Tests, die sich auf jedes Modell und jede Spalte anwenden lassen, indem man sie in einer Beschreibungsdatei in .yml nennt. dbt bringt genau vier eingebaute generische Tests mit:
not_null
Sichert zu, dass eine Spalte keine NULL-Werte enthält. Das ist der grundlegendste Test: Eine Spalte, die immer einen Wert haben soll, darf nicht stillschweigend NULL zulassen.
unique
Sichert zu, dass alle Werte einer Spalte verschieden sind. Wenden Sie das auf jede Spalte eines Primärschlüssels in jedem Modell an, denn doppelte Primärschlüssel führen in den nachgelagerten Aggregationen zu stillem Doppelzählen.
accepted_values
Sichert zu, dass jeder Wert einer Spalte aus einer festgelegten Liste stammt. Nutzen Sie das für Statusfelder, Aufzählungen von Typen und Codes nach ISO.
relationships
Sichert die referenzielle Integrität zu: Jeder Wert einer Spalte existiert in einer bestimmten Spalte eines anderen Modells, also eine Prüfung des Fremdschlüssels. Das fängt verwaiste Datensätze ab, die ein Join sonst still verwerfen würde.
Singuläre Tests: eigene Zusicherungen in SQL
Für fachliche Regeln, die zu speziell sind, um sie zu verallgemeinern, schreiben Sie einen singulären Test, also eine Datei .sql im Verzeichnis tests/ mit einem SELECT, das die verletzenden Zeilen zurückgibt:
-- tests/assert_orders_amount_positive.sql
-- Every completed order must have a positive amount.
select
order_id,
amount
from {{ ref('stg_orders') }}
where status = 'delivered'
and amount <= 0
-- tests/assert_revenue_matches_order_sum.sql
-- The monthly revenue mart must match the sum of staging orders.
select
month,
mart_revenue,
raw_revenue,
abs(mart_revenue - raw_revenue) as discrepancy
from (
select
date_trunc('month', created_at) as month,
sum(amount) as raw_revenue
from {{ ref('stg_orders') }}
where status = 'delivered'
group by 1
) raw
join (
select month, revenue as mart_revenue
from {{ ref('fct_revenue') }}
) mart using (month)
where abs(mart_revenue - raw_revenue) > 0.01
Jede Zeile, die diese SELECT-Anweisungen zurückgeben, ist ein fehlgeschlagener Test. Auch singuläre Tests sind eine Form ausführbarer Dokumentation: Sie machen fachliche Regeln im Code lesbar und prüfbar, statt in Tabellenblättern.
Generische Tests in schema.yml konfigurieren
Generische Tests werden in schema.yml erklärt (oder in jeder anderen Datei .yml im Verzeichnis models/), unter dem Schlüssel tests eines Modells oder einer Spalte:
# models/staging/_staging.yml
version: 2
models:
- name: stg_orders
description: "Cleaned order records from the transactional database."
columns:
- name: order_id
description: "Surrogate primary key for each order."
tests:
- unique
- not_null
- name: status
description: "Current fulfillment status of the order."
tests:
- not_null
- accepted_values:
values: ['placed', 'shipped', 'delivered', 'cancelled', 'returned']
- name: customer_id
description: "Foreign key to stg_customers."
tests:
- not_null
- relationships:
to: ref('stg_customers')
field: customer_id
- name: amount
description: "Order total in USD."
tests:
- not_null
Fahren Sie alle Tests mit dbt test oder die Tests eines bestimmten Modells mit dbt test --select stg_orders. Jeder Test übersetzt zu einer Abfrage in SQL wie dieser:
-- Compiled not_null test on stg_orders.order_id
select count(*) as failures
from (
select order_id
from analytics.staging.stg_orders
where order_id is null
) dbt_internal_test
Der Schweregrad eines Tests: warn oder error
Von Haus aus beendet ein fehlgeschlagener Test dbt test mit einem Statuscode ungleich null und blockiert damit die Pipelines der CI. Über den Schweregrad lässt sich das abstimmen:
# models/staging/_staging.yml
models:
- name: stg_orders
columns:
- name: amount
tests:
- not_null:
severity: warn # warn but do not fail the pipeline
- dbt_utils.expression_is_true:
expression: ">= 0"
severity: error # block pipeline on negative amounts
Nutzen Sie warn für weiche Erwartungen, also Probleme der Datenqualität, von denen Sie wissen wollen, die aber nicht zwingend ein Deployment aufhalten sollen (etwa ein neues, freiwilliges Feld, das noch nicht überall gefüllt ist). Nutzen Sie error (die Voreinstellung) für harte Zusagen, also für Verletzungen, die auf wirklich kaputte Daten hindeuten.
Sie können auch eine Schwelle setzen und den Test erst dann scheitern lassen, wenn der Anteil oder die Zahl der fehlerhaften Zeilen eine Grenze überschreitet. Das ist bei großen Tabellen nützlich, in denen ein kleiner Anteil an NULL-Werten hinnehmbar sein kann:
- name: email
tests:
- not_null:
config:
warn_if: ">10"
error_if: ">100"
Pakete aus der Community: dbt_utils und dbt_expectations
Das Ökosystem der Pakete rund um dbt erweitert die eingebaute Bibliothek an Tests erheblich. Zwei Pakete sind weit verbreitet:
dbt_utils
dbt_utils (gepflegt von dbt Labs) liefert Hilfsmittel, darunter generische Tests, die dbt Core nicht mitbringt:
expression_is_true: prüft einen beliebigen Ausdruck in SQL, etwaamount > 0oderend_date >= start_date.not_constant: sichert zu, dass eine Spalte mehr als einen verschiedenen Wert hat (fängt eingefrorene Zulieferungen ab).recency: sichert zu, dass die Daten innerhalb eines festgelegten Zeitfensters frisch sind.equal_rowcount: sichert zu, dass zwei Modelle gleich viele Zeilen haben.cardinality_equality: sichert zu, dass zwei Spalten dieselbe Menge verschiedener Werte haben.
# dbt_utils examples
- name: end_date
tests:
- dbt_utils.expression_is_true:
expression: ">= start_date"
- name: updated_at
tests:
- dbt_utils.recency:
datepart: hour
field: updated_at
interval: 25
dbt_expectations
dbt_expectations überträgt das Vokabular von Great Expectations auf dbt und bietet über 50 Testarten, darunter:
expect_column_values_to_be_between: sichert zu, dass die Werte in einer Spanne von Zahlen liegen.expect_table_row_count_to_be_between: sichert Grenzen für die Zeilenzahl zu (fängt leere Ladungen ab).expect_column_values_to_match_regex: prüft das Format einer Zeichenkette (E-Mail-Adressen, Codes nach ISO).expect_column_mean_to_be_between: Prüfungen auf die statistische Verteilung.
# dbt_expectations examples
models:
- name: fct_revenue
tests:
- dbt_expectations.expect_table_row_count_to_be_between:
min_value: 1
max_value: 5000
columns:
- name: revenue
tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 10000000
Pakete werden installiert, indem man sie in packages.yml erklärt und dbt deps fährt:
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]
- package: calogica/dbt_expectations
version: [">=0.10.0", "<1.0.0"]
Eine Strategie für die Testabdeckung
Eine gute Strategie für die Testabdeckung wägt Gründlichkeit gegen Pflegbarkeit ab. Nicht jede Spalte braucht jeden Test: Zu viele Tests erzeugen Rauschen und verlangsamen die Builds, ohne die Qualität im gleichen Maß zu heben.
Immer testen
- Primärschlüssel:
uniqueundnot_nullauf dem Schlüssel, der die Granularität jedes Modells bestimmt. Das ist die kleinste noch vertretbare Testabdeckung. - Fremdschlüssel: Tests mit
relationshipsauf allen Spalten, über die Modelle verbunden werden. - Spalten mit Kategorien:
accepted_valuesauf Statusfeldern, Aufzählungen von Typen und Codes nach ISO. - Fachlich verpflichtende Felder:
not_nullauf Spalten, die nach den Regeln der Domäne niemals leer sein dürfen.
Gezielt testen
- Stimmigkeit der Aggregate: singuläre Tests, die Zeilenzahlen oder Summen zwischen den Schichten vergleichen (Staging gegen Mart).
- Aktualität:
dbt_utils.recencyauf Tabellen mit Ereignissen oder Transaktionen, um veraltete Daten zu finden. - Prüfungen auf Spannen:
dbt_expectations.expect_column_values_to_be_betweenfür Spalten mit Zahlen, deren Grenzen bekannt sind.
Auslassen oder zurückstellen
- Felder mit freiem Text, an deren Format keine Erwartung besteht.
- Spalten, die von der Anlage her wirklich freiwillig sind (dünn besetzte Merkmale).
- Tests, die einen vollen Scan von Tabellen im Petabyte-Bereich verlangen würden. Ziehen Sie hier
--limitoder Makros zum Ziehen von Stichproben in Betracht.
Tests in CI/CD fahren
Der empfohlene Befehl für CI/CD ist dbt build, der Modelle und Tests in der Reihenfolge der Abhängigkeiten fährt. Schlägt ein Test fehl, hört dbt auf, nachgelagerte Modelle zu bauen, und verhindert damit, dass schlechte Daten sich weiter ausbreiten.
# Example GitHub Actions workflow step
- name: dbt build (models + tests)
run: |
dbt deps
dbt build --target ci --select state:modified+
env:
DBT_TARGET_SCHEMA: ci_${{ github.run_id }}
Die wichtigsten Praktiken für dbt Tests in CI/CD:
- Schlanke CI: Nutzen Sie
state:modified+, um nur die Modelle und Tests zu fahren, die der aktuelle PR berührt. Das verlangt, die vorigemanifest.jsonaufzubewahren und zu vergleichen. - Eigenes Schema für die CI: Fahren Sie die Builds der CI in einem abgetrennten Schema, damit sie weder die Produktion noch die Entwicklung stören.
- Früh scheitern: Lassen Sie fehlgeschlagene Tests mit dem Schweregrad
errorden Merge in den Hauptzweig blockieren. - Artefakte aufbewahren: Legen Sie
run_results.jsonundmanifest.jsonnach jedem Lauf der CI ab, für den Vergleich im nächsten Lauf und für die Anbindung der Governance.
Die Anbindung an Data Observability
Die Ergebnisse der dbt Tests sind ein erstrangiges Signal für Data Observability, also für die Praxis, Pipelines fortlaufend auf Qualität, Aktualität und Abweichungen im Schema zu überwachen. Plattformen wie Dawiso lesen die Ergebnisse der dbt Tests ein (über run_results.json und das Manifest) und liefern damit aus der Schicht der Governance eine Sicht auf die Datenqualität über die gesamte Schicht der Transformation hinweg.
Im Einzelnen kann Dawiso zeigen:
- welche Modelle von Tests abgedeckt sind und welche ungetestet bleiben, was ein Risiko für die Governance ist;
- wie sich bestandene und fehlgeschlagene Tests über die Zeit entwickeln, um Muster der Verlässlichkeit zu erkennen;
- Werte für die Qualität auf Spaltenebene, hergeleitet aus den Ergebnissen der Tests;
- Alarme, wenn Tests, die zuvor bestanden wurden, zu scheitern beginnen.
Indem die Metadaten der dbt Tests mit den Einträgen im Datenkatalog von Dawiso verbunden werden, sehen Data Consumer nicht nur, was eine Spalte bedeutet (die Beschreibung aus schema.yml), sondern auch, ob sie gerade geprüft ist (die Testabdeckung) und wann sie ihre Prüfungen zuletzt bestanden hat, alles ohne die Oberfläche des Katalogs zu verlassen. Das schließt die Lücke zwischen dem Arbeitsablauf der dbt Tests im Engineering und dem Bedarf der Governance an Nachweisen, dass die Datenbestände die Ansprüche an die Qualität erfüllen, bevor geschäftskritische Prozesse sie nutzen.
Wie sich die Ergebnisse der Tests in das größere Bild aus Lineage und Metadaten fügen, steht im Leitfaden zur Lineage in dbt.
Häufige Antimuster beim Testen, die zu vermeiden sind
- Testen, ohne zu handeln: Tests, die immer warnen und nie untersucht werden, erzeugen Ermüdung gegenüber Alarmen. Jede Warnung braucht einen Owner und einen Plan zur Behebung.
- Primärschlüssel nicht testen:
unique + not_nullauf den Schlüsseln der Granularität auszulassen ist das häufigste Versäumnis, denn doppelte Schlüssel führen in den nachgelagerten Aggregationen zu stillem Doppelzählen. - Erlaubte Werte fest eintragen, ohne sie mit der Quelle abzugleichen: Listen in
accepted_values, die nicht mit den Aufzählungen des Quellsystems im Gleichstand gehalten werden, veralten. Laden Sie Referenzdaten lieber als Seeds und nutzen Sie stattdessen einen Test mitrelationships. - In jedem Lauf der CI alle Tests fahren: In großen Projekten ist es zu langsam, bei jedem Commit alle Tests zu fahren. Nutzen Sie eine schlanke CI mit
state:modified+und fahren Sie die vollständige Testreihe nach Zeitplan, etwa nachts. - Tests auf Staging-Modellen auslassen: Probleme, die in der Schicht des Staging auffallen, sind billiger zu beheben als Probleme, die erst in der Mart-Schicht auffallen. Ergänzen Sie auf Staging-Modellen immer Tests auf den Primärschlüssel und auf not_null.
In Aktion erleben
Data & Analytics Catalog
Schaffen Sie eine einheitliche Sicht auf Ihre Datenbestände und gewinnen Sie mit automatischer Datenerkennung schneller Erkenntnisse.