Accueil dbt Analytics Engineering Tests et qualité des données

Cet article fait partie de notre guide complet sur dbt.

Tests et qualité des données avec dbt

Découvrez comment implémenter une stratégie de testing robuste pour garantir la fiabilité et la qualité de vos transformations de données.

La qualité des données est fondamentale pour des décisions business fiables. dbt transforme le testing de données de corvée manuelle en processus automatisé et intégré.

Chaque test dbt est une assertion SQL simple : si la requête retourne des lignes, le test échoue. Cette approche permet de valider toutes les hypothèses sur vos données de manière systématique.

Les 4 types de tests dbt

1

Tests génériques

Tests préconfigurés : unique, not_null, accepted_values, relationships

2

Tests personnalisés

Logique métier complexe dans des fichiers SQL dédiés

3

Tests unitaires

Validation isolée avec données mockées (dbt 1.8+)

4

Packages de tests

dbt-utils, dbt-expectations pour tests avancés

1

Tests génériques : les fondamentaux

Les tests génériques sont les 4 validations essentielles prêtes à l'emploi dans dbt. Ils couvrent 80% des cas d'usage de validation de données.

🔑 unique

Validation : Aucune valeur dupliquée dans une colonne

Cas d'usage : Clés primaires, identifiants uniques, emails

# schema.yml
models:
  - name: customers
    columns:
      - name: customer_id
        tests:
          - unique

not_null

Validation : Aucune valeur nulle dans une colonne

Cas d'usage : Champs obligatoires, clés étrangères critiques

# schema.yml
models:
  - name: orders
    columns:
      - name: order_date
        tests:
          - not_null

📋 accepted_values

Validation : Valeurs limitées à une liste prédéfinie

Cas d'usage : Statuts, catégories, énumérations

# schema.yml
models:
  - name: orders
    columns:
      - name: status
        tests:
          - accepted_values:
              values: ['placed', 'shipped', 'completed', 'returned']

🔗 relationships

Validation : Intégrité référentielle entre tables

Cas d'usage : Clés étrangères, relations parent-enfant

# schema.yml
models:
  - name: orders
    columns:
      - name: customer_id
        tests:
          - relationships:
              to: ref('customers')
              field: customer_id

Exemple complet : Table orders

# models/schema.yml
version: 2

models:
  - name: orders
    description: "Table des commandes clients"
    columns:
      - name: order_id
        description: "Identifiant unique de commande"
        tests:
          - unique
          - not_null

      - name: customer_id
        description: "Référence vers le client"
        tests:
          - not_null
          - relationships:
              to: ref('customers')
              field: customer_id

      - name: status
        description: "Statut de la commande"
        tests:
          - not_null
          - accepted_values:
              values: ['placed', 'shipped', 'completed', 'returned']

      - name: order_date
        description: "Date de création de la commande"
        tests:
          - not_null
2

Tests personnalisés : logique métier complexe

Quand les tests génériques ne suffisent plus, créez vos propres validations SQL dans le répertoire tests/.

Principe

  • Fichier .sql dans tests/
  • Requête SELECT qui retourne les lignes problématiques
  • 0 ligne = test réussi
  • > 0 lignes = test échoué

Cas d'usage

  • Règles métier spécifiques
  • Validations sur plusieurs colonnes
  • Cohérence entre tables
  • Calculs et agrégations complexes

Exemple 1 : Montants négatifs interdits

Objectif : Vérifier qu'aucune commande n'a un montant négatif

-- tests/assert_positive_order_amount.sql
-- Ce test échoue s'il trouve des montants négatifs

select
    order_id,
    amount
from
    {{ ref('orders') }}
where
    amount < 0

Exemple 2 : Cohérence temporelle

Objectif : La date de livraison doit être postérieure à la date de commande

-- tests/assert_delivery_after_order.sql
-- Ce test échoue si delivery_date < order_date

select
    order_id,
    order_date,
    delivery_date
from
    {{ ref('orders') }}
where
    delivery_date < order_date
    and delivery_date is not null

Exemple 3 : Cohérence inter-tables

Objectif : Le total des lignes de commande doit correspondre au montant de la commande

-- tests/assert_order_totals_match.sql
-- Vérifie la cohérence entre orders et order_items

with order_calculated_totals as (
    select
        order_id,
        sum(quantity * unit_price) as calculated_total
    from {{ ref('order_items') }}
    group by order_id
),

order_discrepancies as (
    select
        o.order_id,
        o.total_amount as order_total,
        oct.calculated_total,
        abs(o.total_amount - oct.calculated_total) as difference
    from {{ ref('orders') }} o
    join order_calculated_totals oct
        on o.order_id = oct.order_id
    where abs(o.total_amount - oct.calculated_total) > 0.01
)

select * from order_discrepancies
3

Tests unitaires : validation isolée (dbt 1.8+)

Nouveauté dbt 1.8 : Les tests unitaires permettent de tester la logique de transformation avec des données fictives, sans interroger votre entrepôt.

Plus rapides, moins coûteux, plus ciblés sur la logique pure.

Avantages

  • Rapidité - Pas de requête sur l'entrepôt
  • Coût réduit - Aucune consommation compute
  • Isolation - Test de la logique pure
  • Contrôle - Données d'entrée maîtrisées

Cas d'usage

  • Validation des calculs complexes
  • Test des macros personnalisées
  • Logique de transformation métier
  • Cas limite et edge cases

Exemple : Test d'un modèle de calcul de prix final

Testons un modèle qui applique une remise sur le prix de base.

Le modèle à tester

-- models/final_price.sql
select
    product_id,
    price,
    discount,
    case
        when discount > price then 0
        else price - discount
    end as final_price
from {{ ref('base_products') }}

Le test unitaire

# models/schema.yml
models:
  - name: final_price
    tests:
      - unit:
          given:
            - input: ref('base_products')
              rows:
                - {product_id: 1, price: 100, discount: 10}
                - {product_id: 2, price: 50, discount: 60}
          expected:
            rows:
              - {product_id: 1, final_price: 90}
              - {product_id: 2, final_price: 0}

Explication : Le test vérifie que le produit 1 (100-10=90) et le produit 2 (remise > prix = 0) sont calculés correctement selon notre logique métier.

4

Packages de tests : étendre les capacités

La communauté dbt a développé des packages riches en tests avancés pour couvrir des cas d'usage complexes.

🛠️ dbt-utils

Le package de référence avec des macros et tests essentiels.

Tests populaires :

  • equal_rowcount - Compare le nombre de lignes entre tables
  • fewer_rows_than - Vérifie qu'une table a moins de lignes qu'une autre
  • equality - Compare deux requêtes complètement
  • recency - Vérifie la fraîcheur des données
# Installation
packages:
  - package: dbt-labs/dbt_utils
    version: 1.1.1

🎯 dbt-expectations

Inspiré de Great Expectations, collection très riche de validations.

Tests avancés :

  • expect_column_values_to_be_between - Plages de valeurs
  • expect_column_values_to_match_regex - Expressions régulières
  • expect_table_row_count_to_be_between - Contrôle volume
  • expect_column_pair_values_to_be_equal - Comparaison colonnes
# Installation
packages:
  - package: calogica/dbt_expectations
    version: 0.10.0

Exemple dbt-utils : Comparaison de tables

# models/schema.yml
models:
  - name: orders_transformed
    tests:
      # Vérifier que le nombre de commandes n'a pas changé après transformation
      - dbt_utils.equal_rowcount:
          compare_model: ref('orders_source')

      # Vérifier la fraîcheur des données (max 1 jour)
      - dbt_utils.recency:
          datepart: day
          field: created_at
          interval: 1

Exemple dbt-expectations : Validations avancées

# models/schema.yml
models:
  - name: customers
    columns:
      - name: age
        tests:
          # Âge entre 0 et 120 ans
          - dbt_expectations.expect_column_values_to_be_between:
              min_value: 0
              max_value: 120

      - name: email
        tests:
          # Format email valide
          - dbt_expectations.expect_column_values_to_match_regex:
              regex: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"

Exécution et workflow de testing

🧪

dbt test

Exécute tous les tests du projet

dbt test
🎯

Test sélectif

Teste un modèle spécifique

dbt test --select orders
🚀

dbt build

Build + tests en une commande

dbt build

Stratégie de testing recommandée

Phase de développement

  1. 1 Tests génériques sur chaque modèle (unique, not_null)
  2. 2 Tests unitaires pour la logique complexe
  3. 3 dbt test --select model_name à chaque modification

Phase de production

  1. 1 Tests personnalisés pour les règles métier
  2. 2 Packages avancés (dbt-expectations) pour la surveillance
  3. 3 dbt build dans les pipelines CI/CD

Meilleures pratiques

À faire

  • • Tester systematiquement les clés primaires (unique + not_null)
  • • Implémenter des tests sur les colonnes critiques métier
  • • Utiliser des tests personnalisés pour la logique complexe
  • • Intégrer dbt build dans vos pipelines CI/CD
  • • Documenter le contexte métier de chaque test
  • • Commencer simple, enrichir progressivement

💡 Conseils avancés

  • • Utilisez les tests unitaires pour les cas limite
  • • Groupez les tests par criticité (tags)
  • • Surveillez les performances de vos tests
  • • Créez des macros pour les validations récurrentes

À éviter

  • • Négliger les tests sur les modèles downstream critiques
  • • Créer des tests trop lents qui bloquent le développement
  • • Ignorer les échecs de test sans investigation
  • • Dupliquer la logique entre tests génériques et personnalisés
  • • Oublier de tester les transformations de données sensibles

⚠️ Attention

  • • Les tests consomment des ressources compute
  • • Tests trop stricts = faux positifs fréquents
  • • Équilibrer couverture et maintenance
  • • Adapter la stratégie selon l'environnement