Les bugs de macros affectent silencieusement chaque modèle qui appelle la macro. Un calcul d’arrondi erroné dans cents_to_dollars corrompt chaque chiffre financier du projet. dbt fournit deux mécanismes de test pour les macros et un workflow de débogage quand les choses tournent mal.
Modèles de test d’intégration
Avant les tests unitaires natifs de dbt 1.8, l’approche standard était les modèles de test d’intégration : des modèles SQL qui exercent une macro avec des entrées connues et vérifient la sortie. Ceux-ci fonctionnent toujours, et sont utiles pour les macros où l’on souhaite tester le comportement réel de l’entrepôt.
Créez un modèle dans models/tests/ (ou un dossier isolé de façon similaire configuré avec un schéma séparé) :
-- models/tests/test_cents_to_dollars.sql{{ config(materialized='table', schema='dbt_tests') }}
WITH test_data AS ( SELECT 1000 AS amount_cents, 10.00 AS expected UNION ALL SELECT 999 AS amount_cents, 9.99 AS expected UNION ALL SELECT 1 AS amount_cents, 0.01 AS expected UNION ALL SELECT 0 AS amount_cents, 0.00 AS expected)
SELECT amount_cents, {{ cents_to_dollars('amount_cents') }} AS actual, expected, {{ cents_to_dollars('amount_cents') }} = expected AS passedFROM test_dataExécutez avec dbt build --select test_cents_to_dollars et interrogez la table de sortie pour vérifier que toutes les valeurs passed sont true. Vous pouvez aussi ajouter un test singulier qui échoue si des lignes ont passed = false :
-- tests/assert_cents_to_dollars_correct.sqlSELECT *FROM {{ ref('test_cents_to_dollars') }}WHERE passed = falseLa force des tests d’intégration est qu’ils exécutent le SQL compilé réel sur votre entrepôt. Ils détectent les problèmes spécifiques à l’entrepôt — différences de coercition de type, comportement des virgules flottantes sur votre base de données spécifique — que des tests unitaires simulés ne surmonteraient pas.
La faiblesse est qu’ils nécessitent un accès à l’entrepôt et créent de vraies tables. Ils sont plus lents à exécuter et nécessitent un nettoyage. Pour tester la pure logique SQL, les tests unitaires natifs sont plus propres.
Tests unitaires natifs (dbt 1.8+)
dbt 1.8 a introduit des tests unitaires qui permettent de tester le comportement des macros en contexte, dans le cadre d’une vraie transformation de modèle, en utilisant des entrées simulées :
unit_tests: - name: test_cents_to_dollars_conversion model: mrt__finance__orders given: - input: ref('base__shopify__orders') rows: - {order_id: 1, amount_cents: 1000} - {order_id: 2, amount_cents: 50} - {order_id: 3, amount_cents: 1} expect: rows: - {order_id: 1, order__amount_dollars: 10.00} - {order_id: 2, order__amount_dollars: 0.50} - {order_id: 3, order__amount_dollars: 0.01}Cela teste cents_to_dollars dans le contexte où elle apparaît réellement en production — pas isolément, mais en tant que partie de la transformation de modèle qui l’utilise. Le bloc given définit des lignes d’entrée simulées. Le bloc expect définit à quoi devrait ressembler la sortie du modèle. dbt construit le modèle en utilisant uniquement les entrées simulées et compare le résultat.
La macro est testée telle qu’elle est réellement utilisée, ce qui détecte les problèmes d’intégration qu’un test isolé pourrait manquer. Si le modèle transmet les entrées via un nom de colonne différent, c’est un bug que vous détecerez ici.
Pour les tests unitaires centrés sur le comportement des macros, gardez les données simulées minimales et représentatives. Incluez les cas limites : valeurs zéro, échelle maximale attendue, tout traitement des nulls que la macro est censée fournir. Voir Tests unitaires vs tests data dans dbt pour des conseils sur les modèles qui justifient l’investissement dans l’écriture de fixtures de tests unitaires.
Le workflow compiler-et-inspecter
Quand une macro produit une sortie inattendue, dbt compile est votre outil de débogage le plus rapide. Exécuter :
dbt compile --select model_nameproduit le SQL entièrement rendu dans target/compiled/[project]/[path]/model_name.sql. C’est ce qui est effectivement envoyé à votre entrepôt.
Les macros échouent de deux façons : les erreurs de compilation (syntaxe Jinja, mauvais nombre d’arguments, variables non définies) et les erreurs de logique (le SQL compile mais produit des résultats erronés). Pour les erreurs de logique, lire la sortie compilée vous dit exactement quel SQL votre macro a généré. Si cela semble erroné, la macro est erronée.
Par exemple, si cents_to_dollars générait ROUND(amount_cents / 100, 2) au lieu de ROUND(amount_cents / 100.0, 2), vous le verriez immédiatement dans la sortie compilée — la division entière serait visible directement dans le SQL.
Pendant le développement, compilez avant d’exécuter. Il est plus rapide de détecter les problèmes de macro à la compilation qu’d’attendre que l’entrepôt s’exécute et retourne une erreur.
Ce qu’il faut tester et quand
Testez chaque macro qui implémente de la logique métier — conversion de devises, calcul de remises, génération de clés. Ce sont des macros où une erreur silencieuse a des conséquences métier.
Ne testez pas les macros structurelles simples qui n’ont pas de logique à vérifier — add_audit_columns qui ajoute simplement des colonnes de métadonnées fixes, par exemple. Le test vérifierait juste que les colonnes sont ajoutées, ce que vous verrez immédiatement dans le premier modèle qui l’utilise.
Pour les macros qui sont bien délimitées à une seule responsabilité, les tests sont plus faciles à écrire : vous n’avez besoin de couvrir que la chose unique que fait la macro. Une macro avec explosion de paramètres et branches conditionnelles nécessite une matrice de tests exponentiellement plus grande. C’est un argument supplémentaire pour garder les macros focalisées.
Exécutez les tests unitaires en CI, pas en production. Ils utilisent des données simulées et sont sans intérêt pour la santé des données de production. Excluez-les des exécutions de production :
dbt build --exclude-resource-type unit_testLes modèles de test d’intégration peuvent rester dans les environnements de développement, ou dans une étape CI séparée qui les exécute sur un entrepôt de développement avec des données réelles (mais limitées).