CertSafari
    DBT Analytics Engineering· Lessons

    Domain 5 · Lesson 27/31

    Running dbt tests in development, CI and production

    Implementing various testing steps in the workflow

    15 min read
    3.23% of exam
    7 sources
    Published 12 Oct 2026
    Docs as of 12 Oct 2026

    What you will be able to do

    • Decide which kinds of test belong in development, CI and production runs, and explain why
    • Explain how dbt build orders unit tests, materialization and data tests, and how failures cause downstream nodes to skip
    • Use test_type selectors and indirect selection modes to run the right tests at each stage of the workflow

    Key concept

    Tests as a gate in the build — Tests are steps in the workflow, not a separate report you read afterwards. Inside dbt build, unit tests run before a model is materialized and data tests run after it, and a failure stops the downstream nodes. Where a test sits in that sequence decides how much bad logic or bad data can get through.

    1.Which tests run in development, CI and production

    Writing tests is half the job. The other half is deciding when they run. dbt has two families of test. Data tests are queries that return failing rows from a built model, source, seed or snapshot. Unit tests check a model's SQL logic against small mocked inputs. How to write each one is covered in [the test types lesson](lesson:5.1). Here the question is where each one fits in the path from a developer's branch to the production job.

    Data tests run at two moments. While you write new code, they show whether a changed SQL statement has broken a model that used to pass. When transformations run in production, they check that your assumptions about the source data still hold. A payments source that suddenly sends a fourth payment method is not a code change, and only a production data test will catch it. Those source-level assumptions are the subject of [testing assumptions on models and sources](lesson:5.2).

    Unit tests belong to development and CI only. The fixtures never change, so a test that passed on the pull request will pass again in production, and the only effect of rerunning it is a bill. In development, unit tests support a test-driven loop: write the expected rows, then change the SQL until it produces them. In CI, they stop a merge that would break logic which used to work. To keep them out of production builds, exclude the resource type with --exclude-resource-type. On dbt v1.11 and later, the DBT_ENGINE_EXCLUDE_RESOURCE_TYPES environment variable does the same job.

    Checkpoint 1 of 8· Check yourself

    A team runs unit tests on every pull request and wants its production job to stop spending compute on them. What should it change?

    Checkpoint 2 of 8· Exam question

    A pull request modifies `stg_orders`. The team wants CI to build `stg_orders` and every downstream model in one invocation, and a failing test on `stg_orders` must stop those downstream models from running at all. Which command accomplishes this?

    Sources123

    2.dbt test versus dbt build

    dbt test checks resources that already exist. It runs data tests on models, sources, snapshots and seeds, plus unit tests on SQL models. It does not build anything first. If you run it straight after editing a model, the data tests query whatever version of the table is still in the warehouse. The full command set is covered in [dbt commands](lesson:1.4).

    dbt build combines the steps. It runs models, tests tests, snapshots snapshots and seeds seeds, all in DAG order. Each resource is tested soon after it is built, so a node never starts on top of an upstream node that has not been checked yet. The task also writes a single run results artifact covering every model, test, seed and snapshot it selected. A CI system can read that one file to decide whether the run passed.

    A dbt build run: the seed is tested before anything else uses it, and the model's tests run right after the model is createdtext
    18:49:43 | 1 of 7 START seed file dbt_jcohen.my_seed............................ [RUN]
    18:49:43 | 1 of 7 OK loaded seed file dbt_jcohen.my_seed........................ [INSERT 2 in 0.09s]
    18:49:43 | 2 of 7 START view model dbt_jcohen.my_model.......................... [RUN]
    18:49:43 | 2 of 7 OK created view model dbt_jcohen.my_model..................... [CREATE VIEW in 0.12s]
    18:49:43 | 3 of 7 START test not_null_my_seed_id................................ [RUN]
    18:49:43 | 3 of 7 PASS not_null_my_seed_id...................................... [PASS in 0.05s]
    18:49:43 | 4 of 7 START test unique_my_seed_id.................................. [RUN]
    18:49:43 | 4 of 7 PASS unique_my_seed_id........................................ [PASS in 0.03s]
    18:49:43 | 5 of 7 START snapshot snapshots.my_snapshot.......................... [RUN]
    18:49:43 | 5 of 7 OK snapshotted snapshots.my_snapshot.......................... [INSERT 0 5 in 0.27s]
    18:49:43 | 6 of 7 START test not_null_my_model_id............................... [RUN]
    18:49:43 | 6 of 7 PASS not_null_my_model_id..................................... [PASS in 0.03s]
    18:49:43 | 7 of 7 START test unique_my_model_id................................. [RUN]
    18:49:43 | 7 of 7 PASS unique_my_model_id....................................... [PASS in 0.02s]

    When a model has unit tests, dbt build adds a step in front of materialization. The unit tests run against the model's SQL first. The model is materialized only if they pass, and its data tests run after that. If the logic is broken, the build stops before it pays to write a full table that would be wrong anyway.

    Checkpoint 3 of 8· Put it in order

    Put the steps dbt build performs for a SQL model with unit tests in order

    1. 1.Data tests are run on the model
    2. 2.The model is materialized
    3. 3.Unit tests are run on the SQL model

    Sources45

    3.How a failing test stops the rest of the DAG

    dbt build uses test failures as a gate. If a test on model_a fails, every resource downstream of model_a is marked SKIP. Duplicate keys that slip into a staging model stop there, instead of spreading into every mart that joins on them. Running dbt run and then dbt test gives you the same tests but not the gate. By the time the tests report a problem, the downstream tables have already been rebuilt from the bad data.

    Some tests check things you want to know about without stopping the pipeline, such as a soft data-quality signal. For those, set the test's severity, or its thresholds, to warn instead of error. A warning appears in the results and downstream nodes still run.

    Tests with more than one parent follow a narrower rule. A relationships test between model_a and model_b depends on both models, and model_b already depends on model_a. In that case the test blocks and skips only the children of the most-downstream parent, which is model_b. If a test's parents are independent of each other, dbt skips a downstream node only when that node depends on all of those parents.

    A failure in development raises a practical question: which rows failed? The --store-failures flag, or the store_failures config, saves each failing test's result rows to a table. By default the table goes in a schema named or suffixed dbt_test__audit. Each new run of a test replaces the failures stored for that test, so the table always shows the latest run.

    Checkpoint 4 of 8· Check yourself

    A not_null test on stg_payments flags a few rows each week. The team wants to see those rows but does not want them to stop the marts built on stg_payments during dbt build. What is the right change?

    Checkpoint 5 of 8· Exam question

    A `not_null` test on `stg_events.session_id` fails for a small, accepted percentage of rows that the data team already knows about, but they still want every CI run to surface how many rows are affected instead of silently ignoring the column. Which two changes together achieve this?(Select 2)

    Sources52

    4.Selecting the right tests for each stage

    The test_type selector looks at a property of the test itself, not at the model it is attached to. That makes it the tool for running each kind of test where it belongs. test_type:unit and test_type:data split the two families. test_type:generic and test_type:singular split data tests further, into YAML-defined assertions and the custom SQL files in tests/. These selectors work with --select and --exclude on both dbt test and dbt build.

    Splitting a test run by family with the test_type selectorbash
    # run data and unit tests
    dbt test
    
    # run only data tests
    dbt test --select test_type:data
    
    # run only unit tests
    dbt test --select test_type:unit
    Which tests to run at each stage of the workflow
    StageTypical selectionWhy
    Development, while changing tricky SQLdbt test --select "test_type:unit"Fast feedback on logic before the full model is materialized
    CI, before mergedbt build (unit tests, then the model, then its data tests)Changed logic and existing assumptions are both checked, and the run fails on a broken change
    Productiondbt build with --exclude-resource-type for unit tests, or dbt test --select "test_type:data"Source data changes between runs; unit test fixtures never do

    You can combine a selector with a model to narrow the run. A comma means intersection, so "one_specific_model,test_type:data" runs only the data tests on that model. This is useful when you have just rebuilt one model and want to check its outputs without waiting for the whole project's test suite.

    Checkpoint 6 of 8· Fill the gap

    You have rewritten the regex in one model and want to run only that model's unit tests. Which value completes the command?

    dbt test --select "one_specific_model,test_type: ? "

    Sources65

    5.Testing a subset of the DAG with indirect selection

    When you select a model, dbt also picks up the tests that depend on it. This is indirect selection, so dbt build -s model_a both runs model_a and tests it. The difficulty is tests with several parents. Under the default mode, eager, a test runs if any of its parents is selected, even if its other parents were never built in this run. A relationships test from orders to an unbuilt customers model can then fail for reasons that have nothing to do with your change.

    Three other modes narrow the selection. buildable runs a test only when everything it references is a selected node or an ancestor of one. That suits a test comparing an aggregate with its own input. cautious runs only tests whose every parent is selected. empty runs no tests at all. You set the mode with --indirect-selection on either dbt test or dbt build. Exclusion always applies to every parent: if any parent is explicitly excluded, the test is excluded too.

    The same selection under the narrower indirect selection modesbash
    dbt test --select "orders" --indirect-selection=cautious
    dbt build --select "orders" --indirect-selection=cautious

    Checkpoint 7 of 8· Match them up

    Match each indirect selection mode to the tests it runs for a selected model

    Tap a term, then the definition that fits it.

    Checkpoint 8 of 8· Exam question

    The team added new unit tests for `dim_customers` and wants to run just those unit tests before merging, without re-running the model's existing data tests. Which single command does this?

    Sources56

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.Unit tests should run in the production job too, so production has the fullest coverage.Why is that wrong?

      Unit tests use static fixtures, so a production run repeats the CI result and only costs compute. Run them in development and CI, and exclude them from production builds.

      Covered in Which tests run in development, CI and production

    2. 2.In dbt build, unit tests run after the model is materialized, alongside its data tests.Why is that wrong?

      Unit tests run first. The model is materialized only if they pass, and its data tests run after that.

      Covered in dbt test versus dbt build

    3. 3.dbt build covers everything, including source freshness checks.Why is that wrong?

      Freshness is not part of build. It needs its own dbt source freshness step or the job's freshness checkbox.

      Covered in dbt test versus dbt build

    4. 4.A failing test in dbt build is only reported, and downstream models still build.Why is that wrong?

      A test failure at error severity causes the downstream resources to skip. Only changing the severity to warn lets them run.

      Covered in How a failing test stops the rest of the DAG

    Practise it for real

    Run your project's tests the way each stage of the workflow would, and watch the build act as a gate

    1. 1.Run dbt test --select test_type:unit in your development target.

      Why: This is the fast development loop. Only the SQL logic is checked against fixtures.

      You should see: Only unit_test nodes appear in the output. No data tests run and no models are built.

    2. 2.Run dbt build -s one_specific_model, replacing the name with a model in your project that has both unit tests and data tests.

      Why: Indirect selection brings in the model's tests, and build orders them around materialization.

      You should see: The unit tests run before the model is created and the data tests run after it.

    3. 3.Break a column so that a unique test on an upstream model fails, then run dbt build again for that model and its children.

      Why: This shows the skip-on-failure gate working.

      You should see: The test reports FAIL and the downstream models report SKIP.

    4. 4.Set that test's severity to warn and rerun the build.

      Why: Severity decides whether a failure blocks the DAG.

      You should see: The test shows as WARN and the downstream models build.

    5. 5.Run dbt test --select "one_specific_model,test_type:data" with your model's name.

      Why: This is the production-style check: data tests on one model, with no unit tests.

      You should see: Only the generic and singular data tests attached to that model run.

    Stuck? Get a nudge

    If a relationships test fails during a partial build because its other parent was never built, rerun with --indirect-selection=cautious.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “whenever you run your transformations in production (to ensure that your assumptions about your source data are still valid)”
      ↩︎ Which tests run in development, CI and production
    2. 2.
      “You should run your data tests whenever you are writing new code (to ensure you haven't broken any existing models by changing SQL)”
      ↩︎ Which tests run in development, CI and production
      “Test result tables are created in a schema suffixed or named dbt_test__audit, by default.”
      ↩︎ How a failing test stops the rest of the DAG
      “A test's results will always replace previous failures for the same test.”
      ↩︎ How a failing test stops the rest of the DAG
    3. 3.
      “Use the resource type flag --exclude-resource-type”
      ↩︎ Which tests run in development, CI and production
      “Use them in development for a test-driven approach and CI to ensure changes don't break them.”
      ↩︎ Which tests run in development, CI and production
      “dbt Labs strongly recommends only running unit tests in development or CI environments.”
      ↩︎ Exam trap 1
      “Since the inputs of the unit tests are static, there's no need to use additional compute cycles running them in production.”
      ↩︎ Prediction
      “to exclude unit tests from your production builds and save compute.”
      ↩︎ Checkpoint
    4. 4.
      “It expects that you have already created those resources through the appropriate commands.”
      ↩︎ dbt test versus dbt build
    5. 5.
      “In DAG order, for selected resources or an entire project.”
      ↩︎ dbt test versus dbt build
      “The run results will include information about all models, tests, seeds, and snapshots that were selected to build, combined into one file.”
      ↩︎ dbt test versus dbt build
      “that test will block-and-skip children of the most-downstream parent only (model_b).”
      ↩︎ How a failing test stops the rest of the DAG
      “Unit tests and data tests can be selected using --select test_type:unit or --select test_type:data for dbt build (same for the --exclude flag).”
      ↩︎ Selecting the right tests for each stage
      “Remember that tests support indirect selection, so dbt build -s model_a will both run and test model_a.”
      ↩︎ Testing a subset of the DAG with indirect selection
      “This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”
      ↩︎ Key concept
      “This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”
      ↩︎ Exam trap 2
      “Tests on upstream resources will block downstream resources from running, and a test failure will cause those downstream resources to skip entirely.”
      ↩︎ Exam trap 4
      “This saves on warehouse spend as the model will only be materialized if the unit tests pass successfully.”
      ↩︎ Checkpoint
      “Tests on upstream resources will block downstream resources from running, and a test failure will cause those downstream resources to skip entirely.”
      ↩︎ Prediction
      “Don't want a test to cause skipping? Adjust its severity or thresholds to warn instead of error”
      ↩︎ Checkpoint
    6. 6.
      “running only unit tests during development for fast feedback, or running only data tests in production where unit test compute isn't needed.”
      ↩︎ Selecting the right tests for each stage
      “Restricts tests to only those that exclusively reference selected nodes.”
      ↩︎ Testing a subset of the DAG with indirect selection
      “Test exclusion is always greedy: if ANY parent is explicitly excluded, the test will be excluded as well.”
      ↩︎ Testing a subset of the DAG with indirect selection
      “useful when you're only building a subset of your DAG, and you want to avoid test failures in eager mode caused by unbuilt resources.”
      ↩︎ Checkpoint

    Also cited

    Ready to test yourself?

    Practise the 11 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.