Data engineer, Paris · Airflow · ClickHouse · dbt · Kubernetes · Snowflake · Databricks · Spark · AWS · GCPData engineer, Paris · Airflow · ClickHouse · dbt · Kubernetes · Snowflake · Databricks · Spark · AWS · GCP
DBT2026-06-30

dbt : tests d'intégration vs pytest, quelle approche choisir

Tahirintsoa Mamitiana·4 min de lecture

Le problème

Les tests natifs de dbt (unique, not_null, relationships, tests génériques custom) couvrent très bien la validation de contraintes sur un modèle isolé. Mais dès qu'il s'agit de vérifier une logique métier qui traverse plusieurs modèles (par exemple que la somme des lignes d'une table de faits agrégée correspond à la source, ou qu'un changement de schéma amont ne casse pas silencieusement un calcul en aval), les tests dbt YAML deviennent vite illisibles ou insuffisants. La question qu'on s'est posée : faut-il pousser dbt plus loin, ou basculer une partie de la validation vers une suite pytest externe ?

Option 1 : tests dbt natifs

Les tests génériques (schema tests) suffisent pour la majorité des cas de validation de données :

# models/marts/schema.yml
models:
  - name: fct_orders
    columns:
      - name: order_id
        tests:
          - unique
          - not_null
      - name: customer_id
        tests:
          - relationships:
              to: ref('dim_customers')
              field: customer_id

Pour des règles plus spécifiques, un test singulier (une requête SQL qui doit retourner zéro ligne) reste dans le même langage que les modèles, versionné au même endroit :

-- tests/assert_order_totals_positive.sql
select order_id, total_amount
from {{ ref('fct_orders') }}
where total_amount < 0

Avantages : colocalisé avec le modèle, exécuté par dbt test dans le même run CI que le build, zéro dépendance supplémentaire, lisible par n'importe qui qui connaît déjà dbt.

Limites : difficile d'exprimer des assertions qui comparent plusieurs runs dans le temps, qui font des appels à des APIs externes, ou qui nécessitent une logique Python (parsing, calculs statistiques). Le passage à l'échelle sur des règles métier complexes rend le YAML/SQL vite difficile à maintenir.

Option 2 : pytest en complément

On a mis en place une suite pytest légère qui tourne après dbt build en CI, avec un client de connexion à l'entrepôt (on utilise le dbtRunner programmatique de dbt-core pour rester dans le même contexte de profil/target) :

# tests/test_revenue_consistency.py
import pytest
from tests.warehouse_client import query
 
def test_daily_revenue_matches_source():
    result = query("""
        select
            (select sum(total_amount) from marts.fct_orders) as mart_total,
            (select sum(amount) from staging.stg_raw_payments) as source_total
    """)
    row = result[0]
    # Tolérance pour les remboursements comptabilisés différemment en amont
    assert abs(row["mart_total"] - row["source_total"]) < row["source_total"] * 0.001
 
@pytest.mark.parametrize("env", ["staging", "production"])
def test_no_orphan_customers(env):
    result = query(f"""
        select count(*) as orphans
        from {env}.fct_orders o
        left join {env}.dim_customers c using (customer_id)
        where c.customer_id is null
    """)
    assert result[0]["orphans"] == 0

Avantages : logique Python complète disponible (paramétrage, comparaison entre environnements, appels externes), intégration naturelle avec le reste de la CI existante (souvent déjà pytest pour le code applicatif), meilleure lisibilité pour des assertions complexes avec tolérances ou calculs multi-étapes.

Limites : plus loin des modèles dans le repo, duplication possible avec certains tests dbt basiques si on n'est pas rigoureux sur la répartition, courbe d'apprentissage pour l'équipe analytics qui ne connaît pas forcément pytest aussi bien que le SQL/YAML dbt.

Comparatif

Critère Tests dbt pytest
Rapidité de mise en place Très rapide pour des règles simples Setup initial plus lourd (client warehouse, fixtures)
Couverture de cas complexes Limitée (SQL pur) Large (logique Python, comparaisons multi-sources)
Intégration CI Native (dbt test) Nécessite une étape CI dédiée après le build
Lisibilité pour l'équipe data Élevée Dépend de la familiarité avec pytest

Ce qu'on utilise en prod et pourquoi

On garde les tests dbt natifs pour tout ce qui est contrainte de données locale à un modèle : unicité, nullité, intégrité référentielle, ranges de valeurs via dbt_utils.accepted_range. C'est rapide à écrire, ça vit avec le modèle, et ça couvre 80 % des besoins réels de qualité de données.

La suite pytest est réservée aux règles de cohérence transverses (réconciliation source/mart, comparaison inter-environnements, règles métier avec tolérance ou logique conditionnelle) qui seraient soit impossibles soit illisibles en SQL pur. Elle tourne dans un job CI séparé, après le dbt build, avec le même warehouse de test.

Recommandation

Ne pas voir ça comme un choix binaire. Pour une équipe qui démarre, les tests dbt natifs suffisent largement au début et coûtent très peu à mettre en place. La bascule vers pytest devient pertinente au moment où on se retrouve à écrire des tests singuliers SQL de plus en plus tordus pour exprimer une logique qui serait triviale en Python. C'est le signal qu'il est temps d'ajouter une suite pytest à côté, pas de remplacer l'existant.