skills/dbt-testing/SKILL.md
dbt testing strategies using dbt_constraints for database-level enforcement, generic tests, and singular tests. Use this skill when implementing data quality checks, adding primary/foreign key constraints, creating custom tests, or establishing comprehensive testing frameworks across bronze/silver/gold layers.
npx skillsauth add kilo-org/kilo-marketplace dbt-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Transform AI agents into experts on dbt testing strategies, providing guidance on implementing comprehensive data quality checks with database-enforced constraints, generic tests, and custom singular tests to ensure data integrity across all layers.
Activate this skill when users ask about:
Official dbt Documentation: Testing
Implement tests in this order for maximum data quality:
The dbt_constraints package provides database-level enforcement (not just dbt tests):
✅ Database Enforcement - Creates actual constraints in the data warehouse ✅ Performance - Database-level constraints improve query optimization ✅ Data Integrity - Prevents invalid data at all access points (not just dbt) ✅ Documentation - Constraints visible in database metadata and BI tools ✅ Query Optimization - Database can use constraints for better execution plans
Standard dbt tests only validate during dbt test runs. dbt_constraints creates real
database constraints that are enforced 24/7.
Official dbt_constraints Documentation: GitHub - Snowflake-Labs/dbt_constraints
# packages.yml
packages:
- package: Snowflake-Labs/dbt_constraints
version: [">=0.8.0", "<1.0.0"]
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]
Install packages:
dbt deps
Official dbt Docs: Package Management
Required for every dimension:
# models/gold/_models.yml
models:
- name: dim_customers
columns:
- name: customer_id
tests:
- dbt_constraints.primary_key
When primary key spans multiple columns:
models:
- name: fct_order_lines
tests:
- dbt_constraints.primary_key:
column_names:
- order_id
- line_number
Not recommended - no database enforcement:
columns:
- name: product_id
tests:
- not_null
- unique
Limitation: Only validates during dbt test runs, doesn't prevent bad data from other sources.
Ensures referential integrity:
models:
- name: fct_orders
columns:
- name: customer_id
tests:
- dbt_constraints.foreign_key:
pk_table_name: ref('dim_customers')
pk_column_name: customer_id
For facts with multiple dimension relationships:
models:
- name: fct_order_lines
columns:
- name: order_id
tests:
- dbt_constraints.foreign_key:
pk_table_name: ref('fct_orders')
pk_column_name: order_id
- name: product_id
tests:
- dbt_constraints.foreign_key:
pk_table_name: ref('dim_products')
pk_column_name: product_id
- name: customer_id
tests:
- dbt_constraints.foreign_key:
pk_table_name: ref('dim_customers')
pk_column_name: customer_id
Not recommended - no database enforcement:
columns:
- name: customer_id
tests:
- relationships:
to: ref('dim_customers')
field: customer_id
For business keys (non-primary keys that must be unique):
columns:
- name: customer_email
tests:
- dbt_constraints.unique_key
When uniqueness spans multiple columns:
models:
- name: stg_orders
tests:
- dbt_constraints.unique_key:
column_names:
- order_number
- order_source
columns:
- name: order_status
tests:
- not_null
- accepted_values:
values: ["pending", "processing", "shipped", "delivered", "cancelled"]
- name: order_amount
tests:
- not_null
Powerful generic tests from dbt_utils package:
columns:
- name: customer_email
tests:
- dbt_utils.not_null_proportion:
at_least: 0.95 # 95% of rows must have email
- name: order_amount
tests:
- dbt_utils.accepted_range:
min_value: 0
max_value: 1000000
- name: customer_status
tests:
- dbt_utils.not_empty_string
Official dbt_utils Documentation: dbt_utils - Generic Tests
Create reusable test for common patterns:
-- tests/generic/test_positive_values.sql
{% test positive_values(model, column_name) %}
select count(*)
from {{ model }}
where {{ column_name }} <= 0
{% endtest %}
Usage:
columns:
- name: order_total
tests:
- positive_values
- name: quantity
tests:
- positive_values
Another Example: Date Range Test
-- tests/generic/test_recent_data.sql
{% test recent_data(model, column_name, days_ago=30) %}
select count(*)
from {{ model }}
where {{ column_name }} < dateadd(day, -{{ days_ago }}, current_date())
{% endtest %}
Usage:
columns:
- name: order_date
tests:
- recent_data:
days_ago: 7 # Alert if no orders in last 7 days
For complex business logic that doesn't fit generic tests:
-- tests/singular/test_order_dates_sequential.sql
with date_validation as (
select
o.order_id,
o.order_date,
c.signup_date
from {{ ref('fct_orders') }} o
join {{ ref('dim_customers') }} c
on o.customer_id = c.customer_id
where o.order_date < c.signup_date -- Order before signup = invalid
)
select * from date_validation
Test fails if ANY rows are returned.
Revenue Reconciliation:
-- tests/singular/test_revenue_reconciliation.sql
-- Ensure fact table revenue matches source system
with fact_revenue as (
select sum(order_amount) as total_revenue
from {{ ref('fct_orders') }}
where order_date = current_date() - 1
),
source_revenue as (
select sum(amount) as total_revenue
from {{ source('erp', 'orders') }}
where order_date = current_date() - 1
),
comparison as (
select
f.total_revenue as fact_revenue,
s.total_revenue as source_revenue,
abs(f.total_revenue - s.total_revenue) as difference
from fact_revenue f
cross join source_revenue s
)
select * from comparison
where difference > 0.01 -- Tolerance of 1 cent
Referential Integrity Check:
-- tests/singular/test_orphaned_orders.sql
-- Find orders with invalid customer_id (not in dim_customers)
select
o.order_id,
o.customer_id
from {{ ref('fct_orders') }} o
left join {{ ref('dim_customers') }} c
on o.customer_id = c.customer_id
where c.customer_id is null
and o.customer_id != -1 -- Exclude ghost key
Official dbt Documentation: Singular Tests
Focus: Basic data quality at source
models:
- name: stg_tpc_h__customers
columns:
- name: customer_id
tests:
- dbt_constraints.primary_key
- name: customer_email
tests:
- not_null
Keep it simple - just verify source data integrity.
Focus: Business rule validation, calculated fields
models:
- name: int_customers__with_orders
columns:
- name: customer_id
tests:
- dbt_constraints.primary_key
- name: lifetime_orders
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
- name: lifetime_value
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
Add business logic validation - ensure calculated fields make sense.
Focus: Comprehensive constraint enforcement with dbt_constraints
models:
- name: dim_customers
description: "Customer dimension with full history and metrics"
columns:
- name: customer_id
description: "Unique customer identifier"
tests:
- dbt_constraints.primary_key
- name: customer_tier
description: "Customer value classification"
tests:
- accepted_values:
values: ["bronze", "silver", "gold", "platinum"]
- name: customer_email
tests:
- dbt_constraints.unique_key
- name: fct_orders
description: "Order transactions fact table"
columns:
- name: order_id
tests:
- dbt_constraints.primary_key
- name: customer_id
tests:
- dbt_constraints.foreign_key:
pk_table_name: ref('dim_customers')
pk_column_name: customer_id
- name: product_id
tests:
- dbt_constraints.foreign_key:
pk_table_name: ref('dim_products')
pk_column_name: product_id
- name: order_amount
tests:
- not_null
- dbt_utils.accepted_range:
min_value: 0
Maximum enforcement - use all constraint types to ensure production data quality.
Analyze failed test records:
dbt test --store-failures
# dbt_project.yml
tests:
+store_failures: true
+schema: dbt_test_failures
Query failures:
select * from dbt_test_failures.not_null_dim_customers_customer_email
Warn vs Error:
columns:
- name: customer_email
tests:
- dbt_constraints.unique_key:
config:
severity: warn # or 'error' (default)
Severity Behavior:
error: Test failure stops dbt execution (exit code 1)warn: Test failure logs warning but continues (exit code 0)Use warn for:
Test specific model:
dbt test --select dim_customers
Test by type:
dbt test --select test_type:generic # All generic tests
dbt test --select test_type:singular # All singular tests
Test with dependencies:
dbt test --select +dim_customers+ # Test model and all dependencies
Official dbt Documentation: Test Selection
# Run all tests
dbt test
# Build models and test together (recommended)
dbt build # Runs models, then tests
# Test specific model
dbt test --select dim_customers
# Test specific column
dbt test --select dim_customers,column:customer_id
# Test by layer
dbt test --select tag:gold
# Test with failures stored
dbt test --store-failures --select fct_orders
Best Practice: Use dbt build instead of dbt run + dbt test separately.
Add tests as you build models, not after deployment.
Database-level constraints provide:
columns:
- name: customer_tier
description: "Customer segmentation based on lifetime value"
tests:
- accepted_values:
values: ["bronze", "silver", "gold", "platinum"]
config:
severity: error
Before moving to production:
When users ask about testing:
"What tests should I add?"
"Why use dbt_constraints instead of regular tests?"
"How do I debug test failures?"
--store-failures to save failing recordsGoal: Transform AI agents into expert dbt testers who implement comprehensive, database-enforced data quality checks that protect data integrity across all layers and access patterns.
development
Oracle Database guidance for SQL, PL/SQL, SQLcl, ORDS, administration, app development, performance, security, migrations, and agent-safe database workflows. Use when the user asks to write, edit, rewrite, review, format, debug, tune, or explain SQL; create or refactor PL/SQL; use SQLcl, Liquibase, ORDS, JDBC, node-oracledb, Python, Java, .NET, or database frameworks; troubleshoot queries, sessions, locks, waits, indexes, optimizer plans, AWR, ASH, migrations, schemas, users, roles, privileges, backup, recovery, Data Guard, RAC, multitenant, containers, monitoring, auditing, encryption, VPD, or safe agent database operations.
documentation
Patterns for reading and writing oleander Iceberg catalog tables in Spark jobs, including naming conventions, write modes, and catalog hierarchy.
data-ai
Integrate Okta for enterprise identity workflows including OIDC login, group claims, and policy-based access controls. Use when implementing workforce or B2B identity scenarios.
documentation
Use when arranging Apache NiFi processors, process groups, ports, comments, numbering, crossing connections, dense fan-in/fan-out, or reusable readable canvas layouts.