Sources, Tests, and Data Quality
This domain examines how you declare raw data, measure freshness, and encode assumptions as tests. The exam focuses on what dbt actually executes: freshness queries against sources and test queries that return failing rows.
Declaring sources and freshness
Sources are declared in YAML with a source name, database, schema, and tables. Each table can override its identifier. Freshness uses a timestamp column named by loaded_at_field, or a custom query on some adapters, and compares the maximum loaded timestamp with the current time. warn_after and error_after thresholds produce warnings or errors when run with dbt source freshness. Freshness is not a substitute for uniqueness or referential-integrity tests.
Generic and singular tests
Built-in generic tests are unique, not_null, accepted_values, and relationships. They are configured in YAML, can be scoped with where, and can be set to severity: warn or error. The relationships test checks that values in one column exist in a column of another relation, using to: and field:. Singular tests are SQL files that return failing rows; an empty result passes. Custom generic tests are parameterized Jinja macros. store_failures: true writes failing rows to an audit schema for investigation.
Execution behavior
dbt test runs tests after models exist. dbt build interleaves models, tests, seeds, and snapshots in DAG order; a failing test skips downstream nodes. Tests on sources use the source: selector syntax, while model tests use ref() or model names in selection.
Worked example
An orders source has loaded_at_field: _loaded_at, with warn_after: {count: 12, period: hour} and error_after: {count: 24, period: hour}. The order_id column gets unique and not_null; status gets accepted_values for a fixed list; customer_id gets a relationships test to customers. Freshness catches a stalled ingestion pipeline, while the tests catch duplicate keys, nulls, unexpected statuses, and orphaned customers. Together they cover different failure modes.
Traps that cost marks
Freshness cannot run without a timestamp field or configured query. Reversing to and field in a relationships test checks the wrong direction. severity: warn does not fail the job. not_null does not catch empty strings. store_failures requires a configured audit schema and adds storage. Testing a source with ref() instead of source() produces a parse error.