테스트와 문서화
왜 dbt 테스트가 필요한가
데이터 파이프라인은 실행은 되지만 결과가 조용히 오염되는 상황이 더 위험하다. ETL 잡이 실패하면 알람이 울리지만, customer_id에 NULL이 10%씩 섞여도 쿼리는 그냥 실행된다. dbt 테스트는 이 "조용한 오염"을 잡아내는 방어선이다.
dbt 테스트는 크게 세 종류다.
- Generic 테스트: YAML 선언 한 줄로 사용하는 재사용 가능한 테스트. dbt 내장 4개와 패키지 확장이 있다.
- Singular 테스트: 특정 비즈니스 규칙에 맞춤형으로 작성하는 SQL 파일.
- Unit 테스트: dbt 1.8+에서 도입된 변환 로직 정적 검증.
Generic 테스트: 내장 4가지
dbt는 설치 즉시 사용 가능한 4가지 Generic 테스트를 제공한다. YAML에 선언하면 dbt가 SQL 쿼리로 변환해 웨어하우스에서 실행한다.
not_null
컬럼에 NULL이 없는지 검사한다. PK·FK·필수 비즈니스 컬럼에 반드시 건다.
unique
컬럼 값이 중복되지 않는지 검사한다. PK 후보 컬럼 검증에 쓴다.
accepted_values
컬럼 값이 허용 목록 안에만 있는지 검사한다. status 같은 열거형 컬럼에 유용하다.
relationships
외래 키 무결성을 검사한다. orders.customer_id가 customers.customer_id에 반드시 존재하는지 확인하는 식이다.
# models/staging/schema.yml
version: 2
models:
- name: stg_orders
columns:
- name: order_id
tests:
- not_null
- unique
- name: status
tests:
- accepted_values:
values: ['placed', 'shipped', 'returned', 'cancelled']
- name: customer_id
tests:
- not_null
- relationships:
to: ref('stg_customers')
field: customer_iddbt test를 실행하면 각 테스트가 "실패 행을 반환하는 SELECT"로 변환된다. 반환 행이 0이면 통과, 1 이상이면 실패다.
Singular 테스트: 맞춤형 SQL
재사용하지 않는 단발성 비즈니스 규칙은 tests/ 디렉터리에 SQL 파일로 작성한다.
-- tests/assert_payment_positive.sql
-- 결제 금액은 항상 양수여야 한다
select
order_id,
payment_amount
from {{ ref('fct_orders') }}
where payment_amount <= 0파일이 존재하는 것만으로 dbt test가 실행한다. Generic 테스트로 일반화하기 어려운 비즈니스 특화 규칙에 적합하다.
Unit 테스트 (dbt 1.8+)
dbt 1.8에서 도입된 Unit 테스트는 변환 로직 자체를 정적 입력으로 검증한다. 웨어하우스의 실제 데이터에 의존하지 않으므로, 로직이 맞는지 개발 단계에서 빠르게 확인할 수 있다.
# models/marts/schema.yml
unit_tests:
- name: test_fct_orders_vip_discount
model: fct_orders
given:
- input: ref('stg_orders')
rows:
- {order_id: 1, amount: 100, is_vip: true}
- {order_id: 2, amount: 200, is_vip: false}
expect:
rows:
- {order_id: 1, final_amount: 90} # VIP 10% 할인 적용
- {order_id: 2, final_amount: 200}Unit 테스트는 CI 파이프라인에서 실행하기 좋다. DuckDB 어댑터 등에서는 웨어하우스 연결 없이도 실행 가능하다.
테스트 설정: severity, store_failures, where
테스트마다 실패 처리 방식을 세밀하게 조정할 수 있다.
columns:
- name: email
tests:
- not_null:
severity: warn # 실패해도 파이프라인 중단 안 함
- unique:
severity: error # 실패하면 즉시 오류 (기본값)
store_failures: true # 실패 행을 웨어하우스 테이블로 저장
where: "created_at > '2024-01-01'" # 특정 범위만 테스트store_failures: true로 설정하면 dbt가 dbt_test__audit 스키마에 실패 행 테이블을 생성한다. 실패 원인 분석이 훨씬 빠르다.
| 설정 | 설명 | 권장 사용처 |
|---|---|---|
severity: error | 실패 시 파이프라인 종료 (기본) | PK, 무결성 필수 컬럼 |
severity: warn | 실패해도 실행 계속 | 레거시 데이터, 허용 가능한 불일치 |
store_failures: true | 실패 행을 테이블로 저장 | 디버깅, SLA 추적 |
where | 테스트 범위 제한 | 대용량 테이블 비용 최적화 |
dbt-utils: 확장 테스트 패키지
dbt Labs가 오픈소스로 배포하는 dbt-utils 패키지는 내장 4개 외에 16개 이상의 Generic 테스트를 추가한다.
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]자주 쓰이는 테스트:
| 테스트 | 설명 |
|---|---|
expression_is_true | 임의 SQL 표현식이 참인지 검사 |
not_constant | 컬럼 값이 하나뿐이 아닌지 검사 |
at_least_one | NULL이 아닌 값이 최소 1개 존재하는지 |
mutually_exclusive_ranges | 날짜 범위가 겹치지 않는지 검사 |
unique_combination_of_columns | 복합 PK 유니크 검사 |
문서화: YAML 설명과 dbt docs
dbt의 문서화는 모델·컬럼 description 필드 하나에서 시작한다.
version: 2
models:
- name: fct_orders
description: >
주문 팩트 테이블. 주문 원본(stg_orders)과
고객 정보(stg_customers)를 결합한 최종 분석용 테이블.
columns:
- name: order_id
description: "주문 고유 식별자 (PK)"
- name: final_amount
description: "VIP 할인 적용 후 최종 결제 금액 (원화)"설명이 길거나 여러 모델에서 공유해야 할 때는 .md 파일로 분리하고 doc() 함수로 참조한다.
{% docs fct_orders %}
# fct_orders
이 테이블은 **주문 팩트 테이블**입니다. ...
{% enddocs %}models:
- name: fct_orders
description: "{{ doc('fct_orders') }}"dbt docs generate를 실행하면 dbt는 YAML 설명, 컬럼 타입, 테스트 결과, ref()/source() 호출에서 추출한 DAG 정보를 모두 수집해 target/catalog.json을 생성한다. dbt docs serve를 실행하면 localhost:8080에서 로컬 웹 UI가 열린다.
Lineage 그래프에서는 소스부터 최종 mart까지 전체 계보를 클릭하며 탐색할 수 있다. 2025년 기준 dbt Fusion 엔진에서는 컬럼 단위 계보(Column-Level Lineage)도 지원된다.
테스트 전략: 어디에 무엇을 걸어야 하나
모든 컬럼에 모든 테스트를 거는 것은 비효율적이다. 실용적인 기준:
- 모든 PK 컬럼:
not_null+unique - 모든 FK 컬럼:
not_null+relationships - 열거형 컬럼:
accepted_values - 핵심 비즈니스 메트릭: Singular 테스트
- 변환 로직 분기: Unit 테스트 (dbt 1.8+)
Staging 레이어에서 소스 품질을 1차 방어하고, Mart 레이어에서 비즈니스 무결성을 2차 방어하는 2계층 전략이 일반적이다.
한 줄 정리
dbt 테스트는 YAML 선언(Generic), 맞춤형 SQL(Singular), 로직 검증(Unit) 세 계층으로 데이터 품질을 보증하고, dbt docs generate로 DAG 계보가 포함된 문서를 자동 생성한다.
References
- https://docs.getdbt.com/docs/build/data-tests
- https://docs.getdbt.com/reference/resource-properties/data-tests
- https://docs.getdbt.com/best-practices/writing-custom-generic-tests
- https://docs.getdbt.com/docs/build/documentation
- https://docs.getdbt.com/docs/explore/build-and-view-your-docs
- https://docs.getdbt.com/docs/explore/column-level-lineage
- https://www.datafold.com/blog/7-dbt-testing-best-practices/
- https://github.com/dbt-labs/dbt-utils
- https://popsql.com/learn-dbt/dbt-docs
- https://stellans.io/dbt-data-tests-null-accepted-values-relationships-patterns-stellans-best-practices/