Implementing dbt tests: sample questions

3 free practice questions on implementing dbt tests, one of the seven topics in the official v1.11 outline for the dbt Analytics Engineering Certification Exam — an estimated 10% of the exam, or about 7 of its 65 questions. Answers, reasoning and documentation links are all on this page.

Last updated . We revise these pages whenever dbt Labs revises the exam.

SHARE OF THE EXAM~10%our estimate
QUESTIONS HERE31 with runnable SQL
FORMATS SHOWN2of 6 on the exam

What this topic covers

Three subtopics: using generic, singular, custom, custom generic and unit tests across models and sources; testing the assumptions a model depends on; and placing testing steps correctly in the workflow. Unit tests are an addition in the current edition, and they are not data tests — they run against fixtures rather than warehouse rows.

SUBTOPICS, FROM THE OFFICIAL OUTLINE · 3

  1. 01Using generic, singular, custom, custom generic, and unit tests on a wide variety of models and sources
  2. 02Testing assumptions for dbt models and sources
  3. 03Implementing various testing steps in the workflow

Where the marks go

Placement and thresholds are where the marks go. A generic test attached at the wrong level silently never receives a column_name argument, because dbt only supplies it when the test is listed under a column — the hotspot question is precisely that mistake, rendered in a real schema.yml.

Severity is the second one: dbt compiles each test into a query returning a failure count plus should_warn and should_error, both derived from warn_if and error_if. And attaching the same unique and not_null pair at four layers of the lineage is not thoroughness — it is four full column scans for one assertion.

3 sample questions, with answers

Nothing is hidden. Each question shows the correct answer, why it is right, why every other option is wrong, and the documentation page that settles it. Where the answer is a claim about SQL behaviour, there is a query you can run in the browser against a small sample schema.

Q1Discrete option (DOMC)Easy

You will be shown statements about where a singular data test and a generic data test are declared, and how each one is named, one at a time. For each statement, answer YES if it is true, NO if it is false.

In the exam these appear one at a time and you answer YES or NO to each, without seeing the rest. All of them, with their answers, are below.

  • The name dbt gives a singular test is the name of its .sql file.YES
  • dbt ships with unique, not_null, accepted_values, and relationships, so you write no SQL to use them.YES
  • A singular test runs only once you list it under a data_tests key in a YAML properties file.NO
  • A generic test's parametrized query is written inline in the schema.yml entry that applies it.NO
  • A schema.yml entry can pass arguments to the generic test it applies.YES

WHY

A singular test has no separate declaration: you save the query that selects failing rows as a .sql file under the tests directory, and dbt takes the test's name straight from the file name — tests/assert_total_payment_amount_is_positive.sql becomes the test assert_total_payment_amount_is_positive (a). That is the whole registration step; the file's presence in test-paths is what makes it run. A generic test is the opposite arrangement: the query lives once in a test block and is pointed at a resource by name from YAML. Four of them are built in — unique, not_null, accepted_values, and relationships come with dbt, so applying them is a YAML entry and nothing else (b). Because the query in a test block is parametrized, the YAML entry is also where you feed it arguments: accepted_values takes values, relationships takes to and field (e). That naming-versus-arguments split is the practical difference — a singular test hard-codes everything in one file, a generic test takes its specifics from the entry that applies it.

WHY THE OTHERS ARE WRONG

A singular test runs only once you list it under a data_tests key in a YAML properties file.
This inverts how dbt finds singular tests. dbt collects them by scanning the directories in test-paths (default: tests) for .sql files — no YAML entry is involved, and a data_tests entry under a model or column is where generic tests are applied. You may optionally add a properties entry for a singular test to attach configs such as a description, but the test runs under dbt test with or without one.
A generic test's parametrized query is written inline in the schema.yml entry that applies it.
The parametrized query is never inline in the properties file. It lives in a test block in a .sql file — the four built-ins ship inside dbt, and a custom generic test goes in a .sql file you write. The YAML entry only names the test and supplies its arguments, which is precisely what makes one definition reusable across many models and columns.
Q2Discrete option (DOMC)MediumRunnable proof

A data test on dim_customers fails during dbt build. Statements about inspecting that test's failing rows appear one at a time. For each, answer Yes if it is true and No if it is not.

In the exam these appear one at a time and you answer YES or NO to each, without seeing the rest. All of them, with their answers, are below.

  • The terminal output for the failed test prints the path to the test's compiled SQL, which selects the failing rows.YES
  • With store_failures enabled, dbt writes the failing rows to a table in a schema named or suffixed dbt_test__audit by default.YES
  • Enabling store_failures changes where the failing rows live, not whether the test passes or fails.YES
  • Without store_failures, the failing rows themselves are recorded in target/run_results.json.NO
  • store_failures can only be set in dbt_project.yml, not on an individual test.NO

WHY

Every data test compiles to a select that returns the failing rows; dbt wraps that query in a count to decide pass/fail. That is why the failure line in the log points at target/compiled/<project>/.../<test_name>.sql — by default the rows are never persisted, so re-running that compiled query yourself is how you see them. store_failures: true (or the --store-failures flag) changes only the persistence step: dbt first saves the test query's rows to a table, then queries that table to calculate the number of failures. Those tables land in a schema named or suffixed dbt_test__audit by default (overridable with a schema config on the test). Because the counting and severity logic are untouched, a test that failed still fails — you simply get a queryable table of offending rows instead of having to run the SQL by hand.

WHY THE OTHERS ARE WRONG

Without store_failures, the failing rows themselves are recorded in target/run_results.json.
run_results.json carries a failures field that is the *number* of rows the test query returned, alongside status, message, and timing — not the rows themselves. Artifacts record outcomes, not data; the rows only reach the warehouse when store_failures is on.
store_failures can only be set in dbt_project.yml, not on an individual test.
store_failures is an ordinary test config. It can be set under config: on an individual test in a YAML file, on a data_tests: block in dbt_project.yml, inside a custom generic test's {{ config() }}, or supplied at run time with --store-failures; an explicit true/false config takes precedence over the flag.
Q3Multiple choiceMedium

Raw orders land in the jaffle_shop source, which carries unique and not_null tests. Before any model is built, you want to run just those source tests so a bad load fails the job early. Which command do you run?

  • dbt source freshness --select "source:jaffle_shop"
  • dbt build --select "source:jaffle_shop+"
  • dbt test --select "source:jaffle_shop"CORRECT
  • dbt test --select "source:jaffle_shop+"

WHY

The source method selects the source nodes themselves — every table declared under the jaffle_shop source. Under default (eager) indirect selection, dbt test then runs the data tests attached to those sources and nothing else. Because dbt never materializes a source, no model is created or refreshed by this invocation: it reads the raw tables as they were loaded and returns a non-zero exit code if unique or not_null fails. That is what makes it usable as a gate at the top of a job — you learn the load is bad before any staging or mart model has been built on top of it.

WHY THE OTHERS ARE WRONG

dbt source freshness --select "source:jaffle_shop"
dbt source freshness is a different check entirely: it compares the maximum loaded_at value (or warehouse metadata) for each source table against the warn_after and error_after thresholds. It reports staleness, not data quality, and never executes the unique or not_null tests.
dbt build --select "source:jaffle_shop+"
dbt build does run each node's tests before its downstream dependents, but it also materializes every downstream model in the same invocation whenever the source tests pass. The requirement was to run only the source tests, with no models built.
dbt test --select "source:jaffle_shop+"
The + operator extends the selection to every node downstream of the source, so dbt also runs the tests on the staging and mart models. Those tests query whatever versions of those tables already exist in the warehouse, which fails or reports on stale data instead of isolating the raw-load check.

Know whether implementing dbt tests is actually costing you marks.

The free readiness check is weighted like the real exam across all seven topics, so it tells you where you stand on this topic relative to the rest — which is the only version of that question worth answering before you book.

Take the free readiness check20 questions · ~15 minutes · no card

The other six topics

Keep reading

SOURCES

Every source above was read in full and last checked on .