Ajouter un test unitaire pour un modèle dbt
Ressources supplémentaires
- Référence des spécifications - Toutes les clés YAML requises et optionnelles pour les tests unitaires
- Exemples - Exemples de tests unitaires dans différents formats (dict, csv, sql)
- Modèles incrémentiels - Tests unitaires des modèles incrémentiels
- Dépendances éphémères - Tests unitaires des modèles dépendant de modèles éphémères
- Substitutions de cas particuliers - Macros introspectives, variables de projet, variables d'environnement
- Modèles versionnés - Tests unitaires des modèles SQL versionnés
- Avertissements BigQuery - Avertissements spécifiques à BigQuery
- Types de données BigQuery - Gestion des types de données BigQuery
- Types de données Postgres - Gestion des types de données Postgres
- Avertissements Redshift - Avertissements spécifiques à Redshift
- Types de données Redshift - Gestion des types de données Redshift
- Types de données Snowflake - Gestion des types de données Snowflake
- Types de données Spark - Gestion des types de données Spark
Que sont les tests unitaires dans dbt
Les tests unitaires dbt valident la logique de modélisation SQL sur des entrées statiques avant la matérialisation en production. Si un test unitaire échoue pour un modèle, dbt ne matérialisera pas ce modèle.
Quand les utiliser
Vous devez faire un test unitaire pour un modèle :
- En ajoutant des scénarios Modèle-Entrée-Sortie pour la fonctionnalité prévue du modèle ainsi que des cas limites pour éviter les régressions si la logique du modèle est modifiée ultérieurement.
- En vérifiant qu'une correction de bogue résout un rapport de bogue pour un modèle dbt existant.
Autres exemples :
- Quand votre SQL contient une logique complexe :
- Regex
- Calculs de dates
- Fonctions window
- Déclarations
case whenavec plusieurswhen - Troncature
- Jointures complexes (jointures multiples, auto-jointures, ou jointures avec des conditions non triviales)
- Quand vous écrivez une logique personnalisée pour traiter les données d'entrée, similaire à la création d'une fonction.
- Logique pour laquelle des bogues ont été signalés auparavant.
- Cas limites non encore vus dans vos données réelles que vous voulez être sûr de gérer correctement.
- Avant de refactoriser la logique de transformation (surtout si le refactoring est significatif).
- Modèles avec une haute « criticité » (modèles publics, sous contrat ou modèles directement en amont d'une exposition).
Quand ne pas les utiliser
Cas pour lesquels nous ne recommandons pas de créer de tests unitaires :
- Fonctions intégrées testées de manière approfondie par le fournisseur du data warehouse. Si un problème inattendu survient, il est plus probable qu'il soit dû à des problèmes dans les données sous-jacentes plutôt qu'à la fonction elle-même. Par conséquent, les données de fixture dans le test unitaire ne fourniront pas d'informations précieuses.
- Les fonctions SQL standard comme
min(), etc.
- Les fonctions SQL standard comme
Format général
Le test unitaire dbt utilise un trio composé du modèle, des entrées données et des sorties attendues (Modèle-Entrées-Sorties) :
model- lors de la construction de ce modèle- Entrées
given- étant donné un ensemble de sources, seeds et modèles comme conditions préalables - Sortie
expect- alors s'attendre à ce contenu de ligne du modèle comme postcondition
Flux de travail
1. Choisir le modèle à tester
C'est évident -- le titre dit tout !
2. Simuler les entrées
- Créer une entrée pour chacun des nœuds dont dépend le modèle.
- Spécifier les données simulées à utiliser.
- Spécifier le
formats'il diffère de la valeur par défaut (dict YAML).- Consultez la section « Data
formats pour les tests unitaires » ci-dessous pour déterminer quelformatutiliser.
- Consultez la section « Data
- Les données simulées ne doivent inclure que le sous-ensemble de colonnes utilisé dans ce cas de test.
Conseil : Utilisez dbt show pour explorer les données existantes des modèles en amont ou des sources. Cela vous aide à comprendre les structures d'entrée réalistes. Cependant, toujours nettoyer les données d'exemple pour supprimer toute information sensible ou PII avant de les utiliser dans vos fixtures de test unitaire.
# Afficher un aperçu des données du modèle en amont
dbt show --select upstream_model --limit 5
3. Simuler la sortie
- Spécifier les données que vous vous attendez à ce que le modèle crée en fonction de ces entrées.
- Spécifier le
formats'il diffère de la valeur par défaut (dict YAML).- Consultez la section « Data
formats pour les tests unitaires » ci-dessous pour déterminer quelformatutiliser.
- Consultez la section « Data
- Les données simulées ne doivent inclure que le sous-ensemble de colonnes utilisé dans ce cas de test.
4. S'assurer que les modèles en amont existent avant d'exécuter
Les tests unitaires requièrent que les modèles parents directs existent dans le data warehouse. Avant d'exécuter des tests unitaires seuls (dbt test), vérifier que les modèles en amont existent déjà :
# Vérifier si les modèles en amont existent dans le data warehouse
dbt list --select +my_model --exclude my_model --resource-type model
# Ensuite vérifier que les tables/vues existent réellement dans le data warehouse via dbt show ou votre client SQL
dbt show --select upstream_model --limit 1
Si les modèles en amont n'existent pas, ou existent mais ont été modifiés et pas encore actualisés, les construire en utilisant --empty pour créer des versions de schéma uniquement :
# Construire les modèles en amont à bon marché (schéma uniquement, pas de lecture de données)
dbt run --select +my_model --exclude my_model --empty
Avertissement :
--emptyremplace les modèles existants par des versions de schéma uniquement (zéro ligne). Utilisez-le uniquement quand les modèles n'existent pas encore, ou quand des changements de schéma doivent être appliqués. Ne l'utilisez pas si les modèles en amont contiennent des données que vous voulez conserver — cela les effacera.
Sauter cette étape si vous utilisez dbt build --select my_model (recommandé) — il gère le pipeline complet incluant les tests unitaires.
Test unitaire minimal
Supposons que vous ayez ce modèle :
-- models/hello_world.sql
select 'world' as hello
Test unitaire minimal pour ce modèle :
# models/_properties.yml
unit_tests:
- name: test_hello_world
# Toujours une seule transformation à tester
model: hello_world
# Pas d'entrées nécessaires cette fois !
# La plupart des tests unitaires auront des entrées -- voir la section « exemple du monde réel » ci-dessous
given: []
# La sortie attendue peut avoir zéro à plusieurs lignes
expect:
rows:
- {hello: world}
Exécuter les tests unitaires
Exécuter les tests unitaires, construire le modèle et exécuter les tests de données pour le modèle hello_world :
dbt build --select hello_world
Cela économise des dépenses de data warehouse car le modèle ne sera matérialisé et passera aux tests de données que si les tests unitaires réussissent.
Ou exécuter uniquement les tests unitaires sans construire le modèle ou exécuter les tests de données :
dbt test --select "hello_world,test_type:unit"
Ou choisir un test unitaire spécifique par nom :
dbt test --select test_is_valid_email_address
Exclure les tests unitaires des builds en production
dbt Labs recommande fortement d'exécuter les tests unitaires uniquement dans les environnements de développement ou CI. Puisque les entrées des tests unitaires sont statiques, il n'y a pas besoin d'utiliser des cycles de calcul supplémentaires pour les exécuter en production. Utilisez-les lors du développement pour une approche test-driven et en CI pour s'assurer que les changements ne les cassent pas.
Utilisez le drapeau --resource-type --exclude-resource-type ou la variable d'environnement DBT_EXCLUDE_RESOURCE_TYPES pour exclure les tests unitaires de vos builds en production et économiser du calcul.
Exemple plus réaliste
unit_tests:
- name: test_order_items_count_drink_items_with_zero_drinks
description: >
Scénario : Commande sans boissons
Quand la table `order_items_summary` est construite
Étant donné une commande avec rien d'autre qu'un article alimentaire
Alors le nombre d'articles de boisson est 0
# Modèle
model: order_items_summary
# Entrées
given:
- input: ref('order_items')
rows:
- {
order_id: 76,
order_item_id: 3,
is_drink_item: false,
}
- input: ref('stg_orders')
rows:
- { order_id: 76 }
# Sortie
expect:
rows:
- {
order_id: 76,
count_drink_items: 0,
}
Pour plus d'exemples de tests unitaires, voir references/examples.md
Scénarios supportés et non supportés
- dbt supporte uniquement les tests unitaires des modèles SQL.
- Les tests unitaires des modèles Python ne sont pas supportés.
- Les tests unitaires des nœuds non-modèles comme les snapshots, seeds, sources, analyses, etc. ne sont pas supportés.
- dbt supporte uniquement l'ajout de tests unitaires aux modèles de votre projet actuel.
- Les tests unitaires des modèles inter-projets ou des modèles importés d'un package ne sont pas supportés.
- dbt ne supporte pas les tests unitaires des modèles qui utilisent la matérialisation
materialized view. - dbt ne supporte pas les tests unitaires des modèles qui utilisent du SQL récursif.
- dbt ne supporte pas les tests unitaires des modèles qui utilisent des requêtes introspectives.
- dbt ne supporte pas une sortie
expectpour l'état final de la table de base de données après insertion/fusion pour les modèles incrémentiels. - dbt supporte une sortie
expectpour ce qui sera fusionné/inséré pour les modèles incrémentiels.
Bon à savoir
- Les tests unitaires doivent être définis dans un fichier YAML dans votre répertoire
model-paths(models/par défaut) - Les fichiers de fixture pour les tests unitaires doivent être définis dans un fichier SQL ou CSV dans votre répertoire
test-paths(tests/fixturespar défaut) - Inclure toutes les références de modèle
refousourcedans la configuration de test unitaire commeinputs pour éviter les erreurs « node not found » lors de la compilation. - Si votre modèle a plusieurs versions, par défaut le test unitaire s'exécutera sur toutes les versions de votre modèle.
- Si vous voulez faire un test unitaire d'un modèle qui dépend d'un modèle éphémère, vous devez utiliser
format: sqlpour l'entrée du modèle éphémère. - Les noms de table dans le modèle doivent être aliasés pour tester la logique de
join
YAML pour spécifier les tests unitaires
- Pour toutes les clés requises et optionnelles dans la définition YAML des tests unitaires, voir references/spec.md
Entrées pour les tests unitaires
Utilisez les inputs dans vos tests unitaires pour référencer un modèle ou une source spécifique pour le test :
- Pour
input:, utilisez une chaîne qui représente un appelrefousource:ref('my_model')ouref('my_model', v='2')ouref('dougs_project', 'users')source('source_schema', 'source_name')
- Pour les entrées de seed :
- Si vous ne fournissez pas d'entrée pour un seed, nous utiliserons le fichier CSV du seed comme entrée.
- Si vous fournissez une entrée pour un seed, nous utiliserons cette entrée à la place.
- Utiliser des entrées « vides » en définissant les lignes à une liste vide
rows: []- C'est utile si le modèle a une dépendance
refousource, mais ses valeurs sont sans pertinence pour ce test unitaire particulier. Soyez juste prudent si le modèle a une jointure sur cette entrée qui causerait des lignes à être supprimées !
- C'est utile si le modèle a une dépendance
models/schema.yml
unit_tests:
- name: test_is_valid_email_address # c'est le nom unique du test
model: dim_customers # nom du modèle que je fais un test unitaire
given: # les données simulées pour vos entrées
- input: ref('stg_customers')
rows:
- {email: cool@example.com, email_top_level_domain: example.com}
- {email: cool@unknown.com, email_top_level_domain: unknown.com}
- {email: badgmail.com, email_top_level_domain: gmail.com}
- {email: missingdot@gmailcom, email_top_level_domain: gmail.com}
- input: ref('top_level_email_domains')
rows:
- {tld: example.com}
- {tld: gmail.com}
- input: ref('irrelevant_dependency') # dépendance que nous devons reconnaître, mais qui n'a besoin d'aucune donnée
rows: []
...
Data formats pour les tests unitaires
dict est le format par défaut — commencez toujours ici
À moins que vous ayez une raison spécifique d'utiliser un autre format, utilisez dict (YAML inline). C'est la valeur par défaut quand la clé format: est omise.
given:
- input: ref('orders')
# aucune clé format: nécessaire — dict est le format par défaut
rows:
- {order_id: 1, status: completed, amount: 100}
- {order_id: 2, status: pending, amount: 50}
Comment choisir le format
Utiliser dict (défaut) |
Utiliser sql |
Utiliser csv |
|---|---|---|
| Tout le reste — c'est le point de départ | Le modèle dépend d'un modèle éphémère | Utiliser un fichier de fixture externe |
| Le type de données de colonne n'est pas supporté par dict/csv | Le type de données de colonne n'est pas supporté par dict | |
| Fichier de fixture externe avec des types non supportés |
Note critique sur sql : le format sql nécessite de spécifier TOUTES les colonnes dans les données simulées. dict et csv ne nécessitent que les colonnes pertinentes pour le test — beaucoup plus concis.
Exigence critique pour sql : Si l'un de vos entrées ref() ou source() du modèle est un modèle éphémère, vous devez utiliser le format sql pour ces entrées. dict et csv échoueront.
dbt supporte trois formats pour les données simulées au sein des tests unitaires :
dict(défaut) : Valeurs de dictionnaire YAML inline.csv: Valeurs CSV inline ou un fichier CSV.sql: Requête SQL inline ou un fichier SQL.
Pour voir des exemples de chacun des formats, voir references/examples.md
Notes :
- Pour le format
sqlvous devez fournir des données simulées pour toutes les colonnes alors quedictetcsvpeuvent fournir uniquement un sous-ensemble. - Seul le format
sqlpermet de tester unitairement un modèle qui dépend d'un modèle éphémère --dictetcsvne peuvent pas être utilisés dans ce cas. - Il n'y a pas de formats qui supportent Jinja.
Fichiers de fixture
Le format dict ne supporte que les données simulées YAML inline, mais vous pouvez aussi utiliser csv ou sql soit inline soit dans un fichier de fixture séparé. Stocker vos fichiers de fixture dans un sous-répertoire fixtures dans l'un de vos test-paths. Par exemple, tests/fixtures/my_unit_test_fixture.sql.
Quand vous utilisez le format dict ou csv, vous ne devez définir les données simulées que pour les colonnes pertinentes pour vous. Cela vous permet d'écrire des tests unitaires succincts et spécifiques. Pour le format sql, toutes les colonnes doivent être définies.
Cas particuliers
- Tests unitaires des modèles incrémentiels. Voir references/special-cases-incremental-model.md.
- Tester unitairement un modèle qui dépend de modèle(s) éphémère(s). Voir references/special-cases-ephemeral-dependency.md.
- Tester unitairement un modèle qui dépend de macros introspectives, variables de projet, ou variables d'environnement. Voir references/special-cases-special-case-overrides.md.
- Tests unitaires des modèles SQL versionnés. Voir references/special-cases-versioned-model.md.
Avertissements spécifiques à la plateforme/l'adaptateur
Il y a des détails spécifiques à la plateforme nécessaires lors de l'implémentation sur (Redshift, BigQuery, etc). Lire le fichier d'avertissements pour votre base de données (s'il existe) :
Types de données spécifiques à la plateforme/l'adaptateur
Les tests unitaires sont conçus pour tester les valeurs attendues, pas les types de données eux-mêmes. dbt prend la valeur que vous fournissez et tente de la convertir en type de données tel qu'inféré à partir des modèles d'entrée et de sortie.
La façon dont vous spécifiez les valeurs d'entrée et attendues dans vos définitions YAML de test unitaire est largement cohérente entre les data warehouses, avec quelques variations pour les types de données plus complexes.
Lire le fichier des types de données pour votre base de données :
- references/warehouse-bigquery-data-types.md
- references/warehouse-postgres-data-types.md
- references/warehouse-redshift-data-types.md
- references/warehouse-snowflake-data-types.md
- references/warehouse-spark-data-types.md
Désactiver un test unitaire
Par défaut, tous les tests unitaires spécifiés sont activés et seront inclus selon le drapeau --select.
Pour désactiver l'exécution d'un test unitaire, définir :
config:
enabled: false
C'est utile si un test unitaire échoue à tort et doit être désactivé en attente de correction.
Quand un test unitaire échoue
Quand un test unitaire échoue, il y aura un message de journal « actual differs from expected » (la réalité diffère de l'attendu), et il affichera une « data diff » entre les deux :
actual differs from expected:
@@ ,email ,is_valid_email_address
→ ,cool@example.com,True→False
,cool@unknown.com,False
Il y a deux principales possibilités quand un test unitaire échoue :
- Il y avait une erreur dans la façon dont le test unitaire a été construit (faux positif)
- Il y a un bogue dans le modèle (vrai positif)
Il faut un jugement d'expert pour déterminer l'un de l'autre.
Le drapeau --empty
Les parents directs du modèle que vous testez unitairement doivent exister dans le data warehouse avant de pouvoir exécuter le test unitaire. Les commandes run et build supportent le drapeau --empty pour construire des dry runs de schéma uniquement. Le drapeau --empty limite les refs et sources à zéro lignes. dbt exécutera toujours le SQL du modèle contre le data warehouse cible mais évitera les lectures coûteuses des données d'entrée. Cela valide les dépendances et s'assure que vos modèles seront construits correctement.
Utiliser le drapeau --empty pour construire une version vide des modèles pour économiser des dépenses de data warehouse.
dbt run --select "stg_customers top_level_email_domains" --empty
Erreurs courantes
| Erreur | Correction |
|---|---|
| Tester du SQL simple utilisant des fonctions intégrées | Tester unitairement uniquement la logique complexe : regex, calculs de dates, fonctions window, déclarations case multi-conditions |
| Simuler toutes les colonnes dans les données d'entrée | N'inclure que les colonnes pertinentes pour le cas de test |
Utiliser le format sql quand dict fonctionne |
Préférer dict (le plus lisible), utiliser csv ou sql uniquement si nécessaire |
Manquant input pour un ref ou source |
Inclure toutes les dépendances de modèle pour éviter les erreurs « node not found » |
| Tester des modèles Python ou des snapshots | Les tests unitaires supportent uniquement les modèles SQL |