adding-dbt-unit-test

Par dbt-labs · dbt-agent-skills

Crée des définitions de tests unitaires YAML qui simulent les entrées du modèle amont et valident les sorties attendues. À utiliser lors de l'ajout de tests unitaires pour un modèle dbt ou lors de la pratique du développement piloté par les tests (TDD) dans dbt.

npx skills add https://github.com/dbt-labs/dbt-agent-skills --skill adding-dbt-unit-test

Ajouter un test unitaire pour un modèle dbt

Ressources supplémentaires

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 when avec plusieurs when
    • 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.

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) :

  1. model - lors de la construction de ce modèle
  2. Entrées given - étant donné un ensemble de sources, seeds et modèles comme conditions préalables
  3. 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 format s'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 quel format utiliser.
  • 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 format s'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 quel format utiliser.
  • 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 : --empty remplace 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 expect pour 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 expect pour 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/fixtures par défaut)
  • Inclure toutes les références de modèle ref ou source dans la configuration de test unitaire comme inputs 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: sql pour 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 appel ref ou source :
    • ref('my_model') ou ref('my_model', v='2') ou ref('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 ref ou source, 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 !

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 :

  1. dict (défaut) : Valeurs de dictionnaire YAML inline.
  2. csv : Valeurs CSV inline ou un fichier CSV.
  3. 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 sql vous devez fournir des données simulées pour toutes les colonnes alors que dict et csv peuvent fournir uniquement un sous-ensemble.
  • Seul le format sql permet de tester unitairement un modèle qui dépend d'un modèle éphémère -- dict et csv ne 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

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 :

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 :

  1. Il y avait une erreur dans la façon dont le test unitaire a été construit (faux positif)
  2. 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

Skills similaires