RetourTester ses données comme on teste son code, avec dbt
La data quality n’est pas un problème technique, c’est un problème de confiance
Un dashboard qui affiche un chiffre d’affaires faux une seule fois, c’est une équipe métier qui ne fait plus confiance aux données pendant six mois. Le directeur commercial qui a présenté un pipeline gonflé de 15 % en comité de direction ne l’oubliera pas de sitôt. Et quand la confiance est perdue, les équipes reviennent à Excel : le pire scénario possible pour votre investissement data
Le problème est massif. Selon Gartner, le coût de la mauvaise qualité des données s’élève en moyenne à 12,9 millions de dollars par an pour une grande entreprise. Pour une ETI, les ordres de grandeur sont moindres mais les conséquences proportionnellement plus lourdes : une erreur de reporting réglementaire peut entraîner une amende, un audit prolongé, ou une perte de crédibilité auprès des parties prenantes
Et pourtant, la majorité des équipes data ne testent pas leurs données. Pas par négligence, par manque d’outillage adapté. Jusqu’à l’émergence de dbt, tester ses données nécessitait des scripts Python ad hoc, des jobs de validation manuels, ou des outils spécialisés coûteux et complexes
Le parallèle avec le monde logiciel est frappant. Les développeurs ont résolu ce problème il y a vingt ans avec le TDD (Test-Driven Development) et l’intégration continue. Un code qui n’est pas testé n’est pas livré, c’est un principe acquis. Il est temps d’appliquer la même rigueur aux données
dbt rend cette rigueur accessible sans infrastructure supplémentaire : les tests vivent dans le projet, aux côtés des modèles qu’ils protègent
Les tests natifs dbt
dbt intègre nativement un framework de testing déclaratif. Vous déclarez vos tests en YAML, dans les mêmes fichiers que votre documentation, et ils s’exécutent avec une seule commande : dbt test
Les 4 tests génériques
# models/marts/_marts__models.yml
models:
- name: fct_commandes
columns:
- name: commande_id
tests:
- unique
- not_null
- name: client_id
tests:
- not_null
- relationships:
to: ref('dim_clients')
field: client_id
- name: statut
tests:
- accepted_values:
values: ['validee', 'annulee', 'en_cours', 'livree']
- name: montant_ttc
tests:
- not_null
Quatre tests, quatre garanties fondamentales :
unique: chaque valeur decommande_idest unique. Si deux lignes ont le même ID, quelque chose a mal tourné dans votre pipeline (duplication, mauvaisGROUP BY, absence de déduplication en staging).not_null: la colonne ne contient pas de valeurs nulles. Unclient_idnull dans une table de commandes, c’est une commande orpheline qui faussera tous vos agrégats par client.relationships: chaqueclient_iddansfct_commandesexiste dansdim_clients. C’est l’équivalent de la contrainte de clé étrangère, mais testée au niveau de la couche analytique.accepted_values: le statut ne peut prendre que 4 valeurs. Si une cinquième apparaît ("annulé"avec un accent, ou"VALIDEE"en majuscules), le test échoue et vous le savez immédiatement
Ces quatre tests couvrent 80 % des erreurs de données courantes. Ils sont gratuits, triviaux à écrire, et s’exécutent en quelques secondes
Tests custom SQL
Pour les règles métier spécifiques, dbt permet d’écrire des tests en SQL pur. Un test custom est une requête qui retourne les lignes en erreur, si la requête retourne zéro ligne, le test passe :
-- tests/assert_coherence_montants.sql
-- Le montant TTC doit toujours être supérieur ou égal au montant HT
select
commande_id,
montant_ht,
montant_ttc
from {{ ref('fct_commandes') }}
where montant_ttc < montant_ht
-- tests/assert_pas_de_commandes_futures.sql
-- Aucune commande ne peut avoir une date dans le futur
select
commande_id,
date_commande
from {{ ref('fct_commandes') }}
where date_commande > current_date
Les tests custom sont puissants parce qu’ils expriment des règles métier que seuls les analystes connaissent. “Le montant TTC est toujours supérieur au montant HT” n’est pas un test technique, c’est une invariant métier. Si elle est violée, c’est qu’un cas d’usage n’a pas été anticipé (remboursement, avoir, erreur de saisie)
Generic vs singular vs custom : quand utiliser quoi
| Type | Quand l’utiliser | Exemple |
|---|---|---|
| Generic (unique, not_null, etc.) | Sur chaque modèle, systématiquement | Clés primaires, colonnes obligatoires |
| Singular (fichier SQL dédié) | Règles métier spécifiques à un modèle | Cohérence montant HT/TTC |
| Custom generic (macro réutilisable) | Règles métier réutilisées sur plusieurs modèles | “Colonne positive”, “Date pas dans le futur” |
Monter en puissance : dbt-expectations et elementary
Les tests natifs couvrent les fondamentaux. Pour une couverture avancée, deux packages communautaires dominent l’écosystème
dbt-expectations : le Great Expectations de dbt
dbt-expectations porte dans dbt les tests du framework open-source Great Expectations : plus de 50 tests avancés, déclarés en YAML comme les tests natifs
# Exemples de tests dbt-expectations sur un modèle financier
models:
- name: fct_factures
columns:
- name: montant_ttc
tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0
max_value: 1000000
row_condition: "statut != 'avoir'"
- dbt_expectations.expect_column_mean_to_be_between:
min_value: 100
max_value: 5000
- name: date_facture
tests:
- dbt_expectations.expect_column_values_to_be_between:
min_value: '2020-01-01'
max_value: '{{ modules.datetime.date.today().isoformat() }}'
- name: devise
tests:
- dbt_expectations.expect_column_distinct_count_to_equal:
value: 3 # EUR, USD, GBP
tests:
- dbt_expectations.expect_table_row_count_to_be_between:
min_value: 1000
max_value: 500000
Les tests dbt-expectations sont particulièrement utiles pour :
- Les bornes de valeurs : un montant entre 0 et 1M€, une date dans une plage plausible
- Les distributions statistiques : la moyenne d’une colonne doit rester dans une fourchette
- Les comptages : le nombre de lignes attendu, le nombre de valeurs distinctes
- Les cohérences inter-colonnes : la colonne A est toujours supérieure à la colonne B
elementary : l’observabilité data
elementary va au-delà des tests ponctuels, il ajoute une couche d’observabilité continue à votre projet dbt
Les fonctionnalités clés :
- Détection d’anomalies automatique : elementary analyse l’historique de vos données et alerte quand un pattern change (volume de lignes inhabituellement élevé ou bas, valeur moyenne qui dévie, freshness en retard).
- Dashboard intégré : un site statique (comme
dbt docs) qui affiche l’état de santé de vos données, tests passés/échoués, anomalies détectées, tendances. - Alertes Slack/email : notifications automatiques quand un test échoue ou qu’une anomalie est détectée.
- Lineage enrichi : visualisation des dépendances avec l’état de santé de chaque modèle
elementary ne remplace pas les tests dbt, il les complète. Les tests dbt valident des règles explicites (“cette colonne est unique”). elementary détecte des anomalies implicites (“ce mois-ci, il y a 3 fois plus de lignes que d’habitude, est-ce normal ?”)
Coût d’elementary en pratique : le package open-source est gratuit en licence, mais ses matérialisations (anomaly detection, freshness, volume) exécutent des requêtes qui consomment les crédits du warehouse : typiquement 30-80 $/mois sur 80 modèles avec un run quotidien, 200-500 $/mois sur 200+ modèles avec un run horaire. Elementary Cloud (la version SaaS payante) démarre à ~50 $/utilisateur/mois et apporte un dashboard et des alertes natifs en plus du package
Comparatif
| Critère | Tests natifs dbt | dbt-expectations | elementary |
|---|---|---|---|
| Type de validation | Règles explicites simples | Règles explicites avancées | Détection d’anomalies |
| Configuration | YAML natif | YAML (package) | YAML + modèles elementary |
| Nombre de tests | 4 génériques | 50+ | Anomaly detection automatique |
| Coût | Inclus dans dbt | Gratuit (open-source) | Gratuit en licence (coût warehouse marginal, voir note ci-dessus) |
| Cas d’usage idéal | Fondamentaux (clés, nulls) | Règles métier complexes | Monitoring continu |
| Courbe d’apprentissage | Très faible | Faible | Modérée |
Notre recommandation : commencez par les tests natifs (jour 1), ajoutez dbt-expectations dès que vous avez des règles métier à valider (semaine 2), et déployez elementary quand vous passez en production (mois 1)
Avec ces trois niveaux d’outils, la question devient : comment les organiser pour que chaque test soit exécuté au bon moment et avec la bonne priorité ?
La pyramide de tests Data
Les équipes logicielles connaissent la pyramide de tests : beaucoup de tests unitaires rapides en bas, peu de tests end-to-end lents en haut. Le même modèle s’applique aux données
Base : tests schema (rapides, exhaustifs)
Les tests not_null et unique sur les clés primaires de tous les modèles. Exécution : quelques secondes par test. Ils attrapent les erreurs grossières : duplications, données manquantes, types incorrects
Objectif : 100 % de couverture sur les clés de tous les modèles
Milieu : tests métier (ciblés, importants)
Les tests accepted_values, relationships, et les tests custom qui valident la logique métier. Ils s’appliquent aux modèles intermediate et marts critiques, pas à tous les modèles
Objectif : couverture complète sur les marts exposés aux utilisateurs et les intermediate qui portent la logique métier complexe
Sommet : tests de réconciliation (lents, stratégiques)
Les tests qui comparent le résultat final avec la source. Par exemple : “la somme du chiffre d’affaires dans fct_commandes_mensuelles est égale à la somme dans le système ERP source, à 0,1 % près.” Ces tests sont lents (ils requêtent les sources) mais détectent les erreurs systémiques
-- tests/reconciliation_ca_erp.sql
with dbt_total as (
select sum(chiffre_affaires) as ca_dbt
from {{ ref('fct_commandes_mensuelles') }}
where mois >= date_trunc('month', current_date - interval '1 month')
),
erp_total as (
select sum(total_amount) as ca_erp
from {{ source('erp', 'orders') }}
where order_date >= date_trunc('month', current_date - interval '1 month')
and status = 'validated'
)
select
d.ca_dbt,
e.ca_erp,
abs(d.ca_dbt - e.ca_erp) / nullif(e.ca_erp, 0) as ecart_pct
from dbt_total d
cross join erp_total e
where abs(d.ca_dbt - e.ca_erp) / nullif(e.ca_erp, 0) > 0.001 -- seuil 0,1%
Objectif : 1 à 3 tests de réconciliation sur les KPI les plus critiques (chiffre d’affaires, volumes, indicateurs réglementaires)
Exécution sélective
dbt permet de tagger les tests par priorité et de les exécuter sélectivement :
# Tous les tests (CI complet)
dbt test
# Uniquement les tests critiques (pre-merge)
dbt test --select tag:critical
# Tests sur un modèle spécifique (développement)
dbt test --select fct_commandes_mensuelles
Le tagging par priorité (critical, standard, reconciliation) permet d’adapter la durée de la pipeline CI au contexte : tests critiques en 30 secondes pour les PR, suite complète en 5 minutes pour le merge, réconciliation en nightly
Structurer les tests par couches ne suffit pas, encore faut-il que les échecs déclenchent une réaction avant que quiconque ne consulte un dashboard
Alerting et monitoring en production
Les tests ne servent à rien s’ils ne tournent pas. En production, deux stratégies se complètent
Tests bloquants en CI/CD
Avant chaque merge dans la branche principale, la pipeline CI exécute dbt test --select tag:critical. Si un test échoue, le merge est bloqué. C’est le premier filet de sécurité : aucune transformation défectueuse ne passe en production
Tests post-exécution avec alerting
Après chaque dbt build en production (quotidien, horaire, ou événementiel), les tests s’exécutent. Si un test échoue :
- elementary détecte l’échec et l’anomalie éventuelle
- Une alerte est envoyée sur Slack (canal
#data-quality) et par email aux propriétaires du modèle - Le dashboard elementary est mis à jour avec le détail de l’erreur
Ce flux transforme les tests en filet de sécurité opérationnel. Les erreurs sont détectées en minutes, pas en jours. L’équipe métier ne voit jamais un dashboard faux, l’alerte arrive avant que quiconque n’ouvre Metabase En pratique, combien de temps faut-il pour passer d’un projet sans aucun test à une couverture qui change la donne ? Voici ce que nous avons observé chez un client
Retour terrain : de 0 à 150 tests en 2 mois
L’éco-organisme que nous avons accompagné avait un problème récurrent : les équipes métier découvraient les erreurs de données avant l’équipe data. Un reporting mensuel envoyé au régulateur contenait une incohérence que personne n’avait vue. Le déclencheur de notre intervention sur la qualité
Phase 1 (semaine 1-2) : les fondamentaux
Ajout des tests natifs sur tous les modèles existants :
unique+not_nullsur toutes les clés primaires (82 modèles)accepted_valuessur les colonnes de statut (12 colonnes)relationshipsentre les marts et les dimensions (8 relations)
Résultat : 95 tests créés, dont 7 en échec dès la première exécution. Sept erreurs silencieuses qui vivaient dans la production depuis des semaines : des doublons dans une table de collecte, des statuts mal orthographiés, une clé étrangère cassée
Phase 2 (semaine 3-4) : les tests métier
Ajout de tests custom pour les invariants métier critiques :
- Cohérence des volumes déclarés (tonnage collecté ≤ tonnage mis sur le marché)
- Pas de dates dans le futur
- Montants toujours positifs sauf pour les avoirs
- Réconciliation mensuelle avec l’ERP sur le chiffre d’affaires
Résultat : 35 tests custom supplémentaires, dont 2 en échec (un cas d’avoir non géré, une date de collecte futuriste issue d’un fichier opérateur mal formaté)
Phase 3 (mois 2) : monitoring continu
Déploiement d’elementary avec alertes Slack. Configuration de la détection d’anomalies sur les 10 tables les plus critiques (volume de lignes, fraîcheur, distributions)
Les résultats :
Au total, 150 tests et règles actives (schema dbt natif, tests custom SQL, monitoring continu elementary)
| Métrique | Avant | Après | Variation |
|---|---|---|---|
| Tests en production | 0 | 150 | N/A |
| Incidents data/mois découverts par les métiers | 8 | 0,5 | -94 % |
| Délai de détection d’une erreur | 3 jours (moyenne) | < 1 heure | -99 % |
| Incidents détectés par les tests avant impact | 0 % | 95 % | N/A |
| Satisfaction métier (survey interne) | 3,2/10 | 8,1/10 | N/A |
Note : “< 1 heure” désigne le délai entre l’exécution du dbt build planifié et l’alerte Slack/email. Le délai de correction effective varie de quelques minutes (fichier mal formé détecté par un test schema) à plusieurs heures (règle métier non anticipée à creuser avec le sponsor data).
Le chiffre le plus parlant : 95 % des incidents sont désormais détectés par les tests avant que les métiers ne les voient. Les 5 % restants sont des cas limites que les tests ne couvrent pas encore, et chaque incident non détecté se transforme en un nouveau test. Le filet se resserre à chaque erreur
Ces résultats sont atteignables rapidement, à condition d’éviter les erreurs que nous voyons se répéter d’un projet à l’autre
Les 3 pièges du testing data
1. Tester tout avec la même rigueur. 150 tests qui s’exécutent en 15 minutes, c’est une pipeline bloquée pendant 15 minutes à chaque merge. Priorisez : tests critiques en CI (30 secondes), suite complète en post-merge (5 minutes), réconciliation en nightly (15 minutes). Si vous testez tout avec la même priorité, vous ne testez rien efficacement
2. Des tests qui passent toujours. Un test not_null sur une colonne qui n’est jamais null (par construction) ne vous protège de rien. Avant de déployer un test, demandez-vous : “dans quel scénario réaliste ce test échouerait-il ?”. Si vous ne trouvez pas de scénario, le test est inutile. Pire : il vous donne un faux sentiment de sécurité
3. Ignorer les tests lents. Un test de réconciliation qui prend 10 minutes bloque toute la pipeline si vous le mettez dans le CI. Mais si vous le supprimez, vous perdez une couverture critique. La solution : le tagging. Marquez-le tag:nightly, exécutez-le une fois par jour en dehors des heures de travail. Les résultats sont visibles dans le dashboard elementary, pas besoin qu’il bloque le merge
Conclusion
La qualité des données n’est pas un luxe, c’est le prérequis de la confiance. Sans tests, vos dashboards sont des opinions. Avec des tests, ce sont des faits vérifiés
dbt a démocratisé le testing data en le rendant aussi simple que le testing logiciel : déclaratif, intégré au workflow, exécuté automatiquement. Les packages communautaires (dbt-expectations, elementary) étendent cette couverture aux cas avancés et au monitoring continu
L’investissement est modeste : 2 à 4 semaines pour passer de 0 à une couverture solide, et quelques heures par mois pour maintenir et étendre les tests. Le ROI est immédiat : des erreurs détectées en minutes au lieu de jours, des équipes métier qui retrouvent confiance, et un reporting fiable
Nobori accompagne les ETI dans la mise en place de frameworks de qualité data. Découvrir notre offre Data
Sources et méthodologie
- Gartner, “The Cost of Poor Data Quality” (2024) : estimation 12,9M$/an pour les grandes entreprises
- dbt Labs, “Testing best practices” : framework de tests natifs et custom
- dbt-expectations : package communautaire, port de Great Expectations pour dbt
- elementary-data : observabilité data open-source pour dbt
- Retours d’expérience Nobori : données issues de nos missions de qualité data auprès d’ETI (métriques avant/après anonymisées)
Les métriques du retour terrain (éco-organisme) sont issues de mesures réelles sur une période de 3 mois post-déploiement. Le délai de détection “< 1 heure” correspond au temps entre l’exécution du dbt build planifié et l’alerte Slack, pas le délai de correction effective de l’incident, qui dépend de sa nature (de quelques minutes à quelques heures)
Pour aller plus loin
- Commencez par structurer votre projet dbt : les conventions sont un prérequis à des tests efficaces
- Explorez notre expertise Data et nos approches d’industrialisation de la qualité data
Ce sujet vous concerne ?
Évaluez votre maturité en Data & Analytics en 30 min avec un expert senior. Sans engagement

Lead Data
Ce sujet vous intéresse ?
Évaluez votre maturité avec un diagnostic de 30 min.
Newsletter
Restez informé
Analyses Cloud, Data & IA : 1 email par mois, pas plus
Inscription confirmée
Merci ! Vous recevrez notre prochaine analyse directement dans votre boîte mail


