ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Tests d'intégration de packages dbt

Le pattern du sous-projet integration_tests pour tester les packages dbt — utilisation de seeds comme données mock, comparaison des sorties aux résultats attendus et exécution de la suite complète.

Planté
dbttestingdata engineering

Il n’est pas possible de tester un package dbt en isolation car il est conçu pour être installé à l’intérieur d’un autre projet. Les modèles du package référencent des définitions source() qui ont besoin d’un schéma cible, et dbt deps a besoin d’un projet parent dans lequel s’installer. Exécuter dbt run à la racine du package ne fonctionne pas comme dans un projet ordinaire.

La solution est un sous-projet integration_tests/ à l’intérieur du dépôt du package qui installe le package parent comme dépendance locale. Ce pattern est utilisé par chaque package Fivetran, dbt-utils et pratiquement tous les packages communautaires sérieux.

La structure du sous-projet

dbt-my_package/
├── dbt_project.yml # Le package lui-même
├── macros/
├── models/
└── integration_tests/ # Projet dbt séparé
├── dbt_project.yml
├── packages.yml # Référence le parent via local: ../
├── profiles.yml # Optionnel : profils spécifiques CI
├── seeds/
│ ├── mock_events.csv # Données d'entrée
│ └── expected_daily_summary.csv # Sortie attendue
├── models/
│ └── _schema.yml # Tests d'égalité
└── tests/
└── assert_custom_logic.sql # Tests singuliers

La pièce critique est packages.yml :

integration_tests/packages.yml
packages:
- local: ../

Le chemin local: ../ indique à dbt d’installer le répertoire parent comme dépendance de package. Quand vous exécutez dbt deps à l’intérieur d’integration_tests/, dbt crée un lien symbolique vers le projet parent. Les modifications de vos macros et modèles de package sont immédiatement disponibles sans ré-exécuter dbt deps.

Configuration du projet de test

Le dbt_project.yml du projet de test d’intégration configure les seeds pour atterrir dans le bon schéma — correspondant au schéma de source par défaut du package afin que les modèles puissent trouver leurs entrées :

integration_tests/dbt_project.yml
name: 'my_package_integration_tests'
seeds:
my_package_integration_tests:
+schema: my_data # Doit correspondre au schéma de source par défaut du package

C’est la clé de voûte. Les définitions de source de votre package référencent var('my_package_schema', 'my_data'). La configuration des seeds place les données mock dans ce même schéma my_data. Quand les modèles du package s’exécutent, source('my_package', 'events') se résout vers les données CSV seedées.

Si votre package a des tables source avec des identifiants personnalisés, vous aurez peut-être besoin de surcharges au niveau du seed :

seeds:
my_package_integration_tests:
+schema: my_data
mock_events:
+alias: events # Correspond à l'identifiant source

Seeds comme données mock

Créez des fichiers CSV représentant les données source que votre package attend. Ce sont des ensembles de données minimaux — juste assez de lignes pour exercer les transformations et les cas limites.

integration_tests/seeds/mock_events.csv
event_id,event_name,event_timestamp,user_id
1,page_view,2024-01-15 10:00:00,user_001
2,page_view,2024-01-15 10:05:00,user_001
3,purchase,2024-01-15 10:10:00,user_001
4,page_view,2024-01-15 11:00:00,user_002
5,page_view,2024-01-16 09:00:00,user_001

Créez ensuite des CSV de sortie attendue définissant ce que vos modèles devraient produire :

integration_tests/seeds/expected_daily_summary.csv
event_date,unique_users,total_events
2024-01-15,2,4
2024-01-16,1,1

Gardez les seeds ciblés. Vous n’avez pas besoin de centaines de lignes pour valider une transformation. Cinq à dix lignes couvrant le chemin nominal plus deux ou trois cas limites (nulls, doublons, dates limites) sont généralement suffisants.

Comparer la sortie aux résultats attendus

Le test dbt_utils.equality compare la sortie réelle d’un modèle à un seed contenant les valeurs attendues :

integration_tests/models/_schema.yml
models:
- name: my_package__daily_summary
data_tests:
- dbt_utils.equality:
compare_model: ref('expected_daily_summary')

Ce test échoue s’il y a la moindre différence entre la sortie du modèle et le seed attendu — lignes manquantes, lignes supplémentaires ou valeurs différentes. C’est une comparaison complète, pas seulement un comptage de lignes.

Pour les modèles avec des calculs en virgule flottante où l’égalité exacte est irréaliste, utilisez dbt_utils.equality avec un paramètre de précision, ou écrivez un test singulier avec une tolérance :

-- integration_tests/tests/assert_revenue_within_tolerance.sql
SELECT *
FROM {{ ref('my_package__revenue_summary') }} actual
JOIN {{ ref('expected_revenue_summary') }} expected
ON actual.date = expected.date
WHERE ABS(actual.total_revenue - expected.total_revenue) > 0.01

Exécution de la suite

Le workflow de test standard exécute quatre commandes en séquence :

Terminal window
cd integration_tests/
dbt deps # Installer le package parent + toutes les dépendances
dbt seed # Charger les données CSV mock dans le warehouse
dbt run # Exécuter tous les modèles du package
dbt test # Vérifier les sorties par rapport aux attentes

Ou en une seule commande :

Terminal window
cd integration_tests/ && dbt deps && dbt seed && dbt run && dbt test

Chaque étape dépend de la précédente. dbt seed crée les données source mock. dbt run construit les modèles à partir de ces données. dbt test compare les résultats aux sorties attendues.

En développement, vous itérerez fréquemment sur les étapes run + test :

Terminal window
cd integration_tests/ && dbt run -s my_package__daily_summary && dbt test -s my_package__daily_summary

Tester plusieurs configurations d’adapters

Si votre package supporte plusieurs warehouses, le projet de test d’intégration devrait fonctionner avec chacun d’eux. Définissez des profils pour chaque adapter dans profiles.yml ou utilisez des targets spécifiques à l’environnement :

integration_tests/profiles.yml
integration_tests:
target: postgres
outputs:
postgres:
type: postgres
# ...
snowflake:
type: snowflake
# ...
bigquery:
type: bigquery
# ...

Exécutez contre chaque adapter :

Terminal window
cd integration_tests/
dbt deps && dbt seed --target snowflake && dbt run --target snowflake && dbt test --target snowflake

C’est là que les tests matriciels CI/CD deviennent précieux — automatiser les exécutions sur chaque adapter supporté et chaque combinaison de version dbt.

Au-delà des tests d’égalité

Les tests d’égalité sont la base, mais les packages nécessitent souvent une validation supplémentaire :

Les tests de comptage de lignes vérifient que les transformations ne suppriment pas ou ne dupliquent pas inopinément des lignes :

models:
- name: my_package__daily_summary
data_tests:
- dbt_utils.equal_rowcount:
compare_model: ref('expected_daily_summary')

Les tests génériques sur les modèles de packages ajoutent une couche supplémentaire. Appliquez la taxonomie standard des testsunique, not_null, accepted_values — aux modèles de votre package dans le projet de test d’intégration :

models:
- name: my_package__daily_summary
columns:
- name: event_date
data_tests:
- unique
- not_null

Les tests singuliers gèrent les validations complexes qui ne peuvent pas être exprimées comme des tests d’égalité ou des tests génériques :

-- integration_tests/tests/assert_no_negative_user_counts.sql
SELECT event_date
FROM {{ ref('my_package__daily_summary') }}
WHERE unique_users < 0

Workflow de développement

La boucle d’itération standard :

  1. Écrire ou modifier une macro/un modèle dans le package
  2. Ajouter ou mettre à jour des données mock dans integration_tests/seeds/
  3. Ajouter ou mettre à jour les résultats attendus
  4. Exécuter dbt seed && dbt run && dbt test
  5. Itérer jusqu’à ce que les tests passent

Chaque nouvelle fonctionnalité et chaque correction de bug devrait avoir un test correspondant. Les données mock croissent au fil du temps mais restent gérables car chaque CSV ne couvre que les transformations qu’il valide.