Sommaire
Pourquoi les tests DBT changent la validation des données
Le problème des tests manuels à grande échelle
Quand un projet data démarre, vérifier manuellement la qualité des données paraît gérable. Avec 5 modèles, un ingénieur peut écrire des requêtes SQL ad hoc, parcourir les résultats et confirmer que tout semble cohérent. Mais dès que ce projet grossit, cette approche s’effondre. Avec 180 modèles de données ou plus, les tests manuels ne sont plus seulement chronophages : ils deviennent une source de risques en elle-même. Chaque transformation non testée est une porte ouverte à des erreurs silencieuses qui remontent jusqu’aux dashboards de production.
Les tests manuels souffrent de trois défauts structurels : ils dépendent de la mémoire du développeur, ils ne s’exécutent pas automatiquement lors des mises à jour du code, et ils ne laissent aucune trace documentée des règles de validation attendues.
DBT comme couche de test native
DBT (Data Build Tool) traite les tests comme des citoyens de première classe du workflow de transformation. Plutôt que d’externaliser la validation à un outil séparé ou à des processus manuels, dbt permet d’écrire et d’exécuter des tests directement aux côtés des modèles SQL. Ils vivent dans le même dépôt, sont versionnés avec le code et s’exécutent automatiquement via une commande unique.
Cette intégration native change profondément la relation entre développement et qualité des données. Un test dbt n’est pas un contrôle après-coup : il fait partie du contrat que votre modèle doit respecter. Quand une transformation évolue, les tests détectent immédiatement si elle rompt une règle métier définie en amont. Cela permet d’aller vite sans sacrifier la fiabilité, même sur des pipelines complexes.
Comment fonctionnent les assertions SQL dans DBT
Le mécanisme fondamental : zéro ligne = succès
Le principe sur lequel repose tout le système de tests dbt est simple et élégant. Un test est une requête SQL. Si cette requête retourne au moins une ligne, le test échoue. Si elle ne retourne aucune ligne, le test réussit.
Cette logique inversée peut surprendre au premier abord, mais elle est parfaitement adaptée à la validation de données. Pour vérifier qu’aucun identifiant n’est nul dans une table, on écrit une requête qui sélectionne les lignes où l’identifiant EST nul. Si la requête retourne des résultats, quelque chose cloche. Sinon, la règle est respectée.
Voici un exemple concret d’assertion SQL que dbt génère ou interprète pour un test not_null :
SELECT *
FROM {{ model }}
WHERE user_id IS NULL
Si la table contient un seul enregistrement avec user_id = NULL, cette requête retourne une ligne et le test échoue immédiatement.
Pourquoi cette logique est fiable en pratique
Ce fonctionnement s’appuie sur des requêtes SQL standards, sans magie ni abstraction opaque. Cela signifie que n’importe quel data engineer sachant écrire du SQL peut comprendre, auditer et modifier un test dbt. La transparence est totale : vous pouvez inspecter la requête générée dans les logs, la rejouer manuellement dans votre entrepôt de données pour déboguer, et comprendre précisément ce qui a provoqué un échec.
Cette approche permet aussi de construire des tests arbitrairement complexes. Tant qu’une règle métier peut s’exprimer sous forme de requête SQL qui isole les cas invalides, elle peut devenir un test dbt. Cela couvre un spectre très large de contraintes : unicité, intégrité référentielle, plages de valeurs, cohérence temporelle, règles métier spécifiques à un secteur comme la finance ou le marketing.
Les 4 types de tests DBT et quand les utiliser
Tests génériques et tests personnalisés
DBT propose nativement quatre tests génériques préconfigurés, activables en une ligne de YAML dans votre fichier de documentation. unique vérifie qu’une colonne ne contient pas de doublons, ce qui est indispensable pour toute clé primaire. not_null s’assure qu’aucune valeur manquante ne pollue une colonne critique. accepted_values contrôle qu’une colonne contient uniquement un ensemble de valeurs autorisées, par exemple ['active', 'inactive', 'pending'] pour un statut client. Enfin, relationships valide l’intégrité référentielle entre deux tables, comme s’assurer que chaque order_id dans une table de lignes de commande existe bien dans la table des commandes.
Les tests personnalisés prennent le relais quand la logique métier dépasse ces quatre primitives. Vous les écrivez sous forme de fichiers SQL dans le dossier tests/ de votre projet. Par exemple, vérifier qu’un montant de transaction ne dépasse jamais 1 000 000 € sans qu’un flag de validation soit activé est une règle que seul un test personnalisé peut exprimer.
Tests unitaires et packages externes
Depuis DBT 1.8, les tests unitaires permettent de valider la logique interne d’un modèle SQL avec des données d’entrée fictives et des sorties attendues explicitement définies. C’est l’équivalent des tests unitaires en développement logiciel classique, appliqué aux transformations data. Ils sont particulièrement utiles quand une transformation effectue des calculs complexes dont le résultat doit rester stable au fil des évolutions du code.
Les packages externes comme dbt-utils et dbt-expectations étendent le catalogue de tests disponibles. dbt-expectations s’inspire du framework Great Expectations et propose des assertions prêtes à l’emploi sur les distributions statistiques, les formats de chaînes ou les valeurs hors plage. Ces packages s’installent en une ligne dans packages.yml et s’utilisent exactement comme les tests génériques natifs.
| Type de test | Cas d’usage principal | Complexité |
|---|---|---|
| Générique (unique, not_null…) | Contraintes structurelles sur les colonnes | Faible, configuration YAML |
| Personnalisé SQL | Règles métier spécifiques au domaine | Moyenne, requête SQL à écrire |
| Unitaire (DBT 1.8+) | Validation de la logique de transformation | Moyenne à élevée, fixtures de données |
| Packages externes (dbt-expectations) | Assertions statistiques et formats avancés | Faible à moyenne selon l’assertion |
Mettre en place vos premiers tests : étapes pratiques
Configurer les tests dans models.yml
La déclaration des tests se fait dans les fichiers YAML de documentation de vos modèles, généralement nommés models.yml ou schema.yml. La syntaxe est directe. Pour chaque modèle, vous listez ses colonnes et les tests à appliquer :
models:
- name: orders
columns:
- name: order_id
tests:
- unique
- not_null
- name: status
tests:
- accepted_values:
values: ['placed', 'shipped', 'delivered', 'cancelled']
- name: customer_id
tests:
- relationships:
to: ref('customers')
field: id
Ce fichier constitue la documentation vivante de vos modèles. Chaque contrainte déclarée ici est automatiquement vérifiable, ce qui donne à votre équipe une source de vérité sur les règles attendues.
Exécuter les tests et interpréter les résultats
La commande dbt test lance tous les tests de votre projet. Quelques variantes sont utiles au quotidien :
dbt test --select ordersexécute uniquement les tests liés au modèleordersdbt test --select source:raw_crmcible les tests définis sur vos sources de données brutesdbt test --target prodexécute les tests sur l’environnement de production
Quand un test échoue, dbt affiche dans les logs la requête SQL exacte qui a retourné des lignes invalides. Pour déboguer, copiez cette requête et exécutez-la directement dans votre entrepôt. Vous verrez précisément quels enregistrements violent la contrainte, avec leurs valeurs réelles. Une fois le problème identifié, la correction peut venir soit de la transformation (un bug dans le SQL), soit de la donnée source (un problème en amont dans le pipeline), soit du test lui-même (une définition trop stricte par rapport à la réalité métier).
Intégrer les tests dans votre routine de développement
La bonne pratique est d’écrire les tests au même moment que le modèle, pas après. Définir les contraintes pendant la conception du modèle oblige à réfléchir aux règles métier dès le départ et prévient les erreurs de conception en amont. Une commande dbt build exécute à la fois les transformations et les tests dans l’ordre correct des dépendances, ce qui simplifie encore le workflow.
Bonnes pratiques pour une stratégie de tests robuste
Nomenclature et documentation des assertions
Un test sans contexte est difficile à maintenir. Donnez des noms explicites à vos tests personnalisés : assert_orders_amount_not_negative dit immédiatement ce qu’il vérifie, contrairement à test_orders_v2. Dans le fichier YAML, la clé description permet d’ajouter une explication en langage naturel sur la raison d’être de chaque contrainte. Cette documentation est exploitable par dbt docs pour générer un catalogue de données consultable par toute l’équipe.
Intégration dans le CI/CD
Les tests dbt s’intègrent naturellement dans un pipeline CI/CD. L’idée est simple : avant tout déploiement en production, la commande dbt build doit s’exécuter avec succès dans un environnement de test. Si un test échoue, le pipeline bloque le déploiement. Cela garantit que chaque nouvelle version de vos modèles respecte les contrats de données définis, sans intervention manuelle.
Les faux positifs sont un risque réel dans cette configuration. Un test trop strict peut bloquer un déploiement légitime parce qu’une donnée edge case n’avait pas été anticipée. La solution n’est pas de supprimer le test, mais d’affiner son périmètre ou d’ajuster les valeurs attendues. DBT permet de définir des seuils de tolérance via warn_if et error_if pour certains tests : un test peut émettre un avertissement sans bloquer si moins de 5 lignes sont en erreur, et bloquer seulement au-delà.
Prioriser selon la criticité des modèles
Toutes les tables ne méritent pas le même niveau de couverture. Les modèles qui alimentent directement des décisions métier, des rapports financiers ou des indicateurs de performance prioritaires doivent avoir une couverture de tests exhaustive. Les tables intermédiaires de staging méritent au minimum les tests not_null et unique sur leurs clés. Les modèles expérimentaux ou en développement peuvent fonctionner temporairement avec une couverture minimale, du moment que ce choix est explicite et documenté.
Dépasser les tests génériques : personnalisation et packages
Écrire un test SQL personnalisé pour des règles métier
Un test personnalisé dans dbt est un fichier .sql placé dans le dossier tests/. La convention de nommage recommandée est assert_[description].sql. La requête doit retourner les lignes qui violent la règle. Par exemple, pour vérifier qu’une commande ne peut pas avoir une date de livraison antérieure à sa date de création :
SELECT
order_id,
created_at,
delivered_at
FROM {{ ref('orders') }}
WHERE delivered_at < created_at
Ce test échoue dès qu’une commande présente cette incohérence temporelle. Il est lisible, testable manuellement et versionnée avec le reste du code. Pour le rendre réutilisable sur plusieurs modèles, dbt permet de le transformer en test générique paramétré en le plaçant dans le dossier macros/ avec une signature configurable.
dbt-expectations et l’extension du catalogue de tests
dbt-expectations apporte des assertions orientées data quality qu’il serait fastidieux d’écrire à la main. Vérifier qu’une colonne de montants suit approximativement une distribution log-normale, qu’un champ texte respecte un format regex précis, ou qu’un pourcentage de valeurs nulles reste sous un seuil donné : ces cas sont couverts par des tests préconstruits. Le package s’utilise comme les tests natifs, directement dans le YAML. L’équilibre entre tests génériques et tests personnalisés se définit naturellement : les génériques couvrent les contraintes structurelles communes à tous vos modèles, les personnalisés capturent la connaissance métier spécifique à votre domaine.
Erreurs courantes et comment les éviter
Tests trop larges, tests trop étroits
Un test trop large vérifie une règle si générale qu’il passe même quand les données sont partiellement corrompues. Un test not_null sur une colonne qui est rarement nulle mais devrait l’être jamais ne détecte rien si 0,1 % des lignes sont problématiques. Sans seuil explicite, le test passe et le problème reste invisible.
À l’opposé, un test trop étroit valide une règle si précise qu’elle casse au moindre changement anodin : tester la valeur exacte d’un compteur ou d’un total recalculé chaque jour mène inévitablement à des échecs non pertinents qui fatiguent l’équipe et diluent l’attention sur les vrais problèmes.
L’absence de tests sur des projets à grande échelle
Sur un projet avec 180 modèles non testés, un bug dans une transformation intermédiaire peut se propager silencieusement à travers dix modèles dépendants avant d’apparaître sous forme d’anomalie dans un rapport. La détection devient alors un travail d’investigation rétrospectif coûteux. La stratégie pragmatique pour rattraper une dette de tests est de commencer par les modèles les plus en aval, ceux qui alimentent directement les analyses, et de remonter progressivement vers les sources. Ajouter cinq tests bien ciblés par semaine sur les modèles critiques produit rapidement un filet de sécurité utile.
Négliger les tests lors des évolutions de modèles
Les tests ne sont pas écrits une fois pour toutes. Quand la logique d’un modèle évolue, ses tests doivent évoluer avec elle. Une bonne pratique est d’inclure la revue des tests dans toute pull request qui modifie un modèle existant. Les tests unitaires introduits en DBT 1.8 sont particulièrement précieux ici : ils capturent le comportement attendu d’une transformation à un instant donné et signalent immédiatement quand une modification change ce comportement, intentionnellement ou non.
- Toujours écrire ou mettre à jour les tests dans la même pull request que le modèle concerné
- Documenter les assertions complexes avec une description YAML pour faciliter la maintenance future
- Utiliser
warn_ifeterror_ifpour distinguer les anomalies bloquantes des signaux d’alerte - Tester en priorité les colonnes utilisées comme clés de jointure ou comme filtres dans les modèles en aval
- Réviser les tests existants après chaque incident data pour enrichir la couverture là où le problème est apparu
Une bonne couverture de tests dbt n’est pas un objectif à 100 % sur tous les modèles sans distinction. C’est une allocation raisonnée de l’effort de validation là où le risque est le plus fort, maintenue activement au rythme des évolutions du projet.
- Commencer par les modèles qui alimentent directement des décisions ou des KPIs partagés
- Appliquer systématiquement
uniqueetnot_nullsur toutes les clés primaires sans exception - Ajouter des tests personnalisés pour toute règle métier qui a déjà causé un incident en production
- Valider l’intégrité référentielle entre chaque paire de tables liées par une clé étrangère
- Exécuter
dbt testen CI avant chaque merge sur la branche principale pour bloquer les régressions







