dbt a de nombreux avantages, mais je trouve que l’un de ses principaux atouts est de pouvoir mettre en place une stratégie de tests dbt efficace assez rapidement et simplement.
Dans cet article, je vais vous présenter les différents tests dbt qu’il est possible de mettre en place. Mon objectif est qu’à la fin de cet article, vous puissiez y voir plus clair dans toutes les options qui s’offrent à vous et que vous puissiez choisir la bonne technique qui correspond à vos besoins.
Et à la fin de l’article, je vous donnerai différents conseils afin d’exploiter au mieux la mise en place de tests dbt.
Les tests génériques dans dbt
Nous allons débuter par les tests les plus simples à mettre en place : il s’agit des tests génériques dans dbt. dbt offre 4 tests qu’il est possible de mettre en place sans n’avoir rien à installer et de façon très simple. Pour pouvoir les utiliser, il suffit de les inscrire dans la configuration de vos modèles, soit dans les fichiers yml qui configurent vos modèles (ce que je conseille), soit directement dans vos modèles.
Ces tests génériques sont au nombre de 4 :
- not_null : Teste si la colonne sélectionnée ne contient pas de valeurs null
- unique : Teste si toutes les valeurs de la colonne sélectionnée sont bien uniques
- relationships : Teste si toutes les valeurs de la colonne sélectionnée ont bien une correspondance dans la table spécifiée
- accepted_values : Teste si toutes les valeurs de la colonne sélectionnée sont bien contenues dans la liste des valeurs saisies
version: 2
models:
- name: customers
columns:
- name: id
tests:
- not_null
- unique
- name: firstname
- name: lastname
- name: orders
columns:
- name: id
- name: customer_id
tests:
- relationships:
to: ref('customers')
field: id
Dans l’exemple ci-dessus, nous avons deux modèles : customers
et orders
. Dans le modèle customers
, nous nous assurons que le champ id
est bien toujours rempli et que toutes les valeurs sont uniques.
Et dans le modèle orders
, nous vérifions que tous les customer_id
sont bien rattachés à un client.
Les tests singuliers dans dbt
Les tests génériques couvrent déjà une bonne partie des problématiques auxquelles nous pouvons être confrontés. Mais il est également possible de définir nos propres tests dbt, et ceci de deux façons :
- En mettant en place un test singulier
- En définissant des tests génériques personnalisés
La mise en place de tests singuliers est simple à comprendre : on va écrire une requête SQL qui va chercher des erreurs. Si la requête renvoie des lignes, cela signifie que le test a échoué car il a rencontré des erreurs ; sinon, cela veut dire que le test a réussi car aucune erreur n’a été détectée.
Grâce à ces tests singuliers, il est possible de tester des choses spécifiques comme une règle métier. Par exemple, si un client a passé une commande, son statut dans la table des clients doit être customer
. Pour cela, nous pouvons écrire ce test :
SELECT *
FROM orders
JOIN customers USING (customer_id)
WHERE customers.status != 'customer'
Les tests génériques personnalisés
Les tests génériques personnalisés vont nous permettre de pouvoir les utiliser de la même façon que les 4 tests génériques proposés de base par dbt.
Par exemple, on veut écrire un test qui vérifie si toutes les valeurs d’une colonne spécifiée sont bien supérieures ou égales à 0. Nous n’allons pas écrire les tests un par un mais nous allons définir un test générique que nous appliquerons aux différentes colonnes que nous voulons tester (valeurs des commandes, quantité des commandes, le nombre de commandes passées par un client, etc.).
{% test greater_than_zero(model, column_name) %}
WITH validation AS (
SELECT *
FROM {{ model }}
WHERE {{ column_name }} <= 0
)
SELECT *
FROM validation
{% endtest %}
Ensuite, ce test pourra être utilisé dans les fichiers yml comme les autres tests génériques :
version: 2
models:
- name: orders
columns:
- name: amount
tests:
- greater_than_zero
Packages de tests dbt
Il peut parfois être fastidieux d’écrire les différents tests génériques dont on pourrait avoir besoin. Heureusement, il existe des packages qui proposent de nombreux tests génériques que nous pouvons réutiliser dans notre projet (et aussi nous donner des idées de tests à mettre en place).
Par exemple, pour notre test de valeurs supérieures à 0, nous aurions pu utiliser le package dbt_utils
qui contient différents tests que l’on utilise couramment :
version: 2
models:
- name: orders
columns:
- name: amount
tests:
- dbt_utils.accepted_range:
min_value: 0
inclusive: false
Un autre package populaire qui contient de nombreux tests et que j’utilise beaucoup est dbt_expectations
.
Dans un précédent article, je vous présentais un de ces packages de tests qui offrait la possibilité de détecter les anomalies dans vos données via la mise en place de tests dbt. Ce package dbt-elementary
entre dans cette catégorie de tests et c’est également un package que j’utilise beaucoup.
Les tests unitaires dans dbt
Depuis la version 1.8 de dbt, il est désormais possible de mettre en place des tests unitaires. Pour ceux qui viennent du développement, vous savez tout de suite de quoi il s’agit : pouvoir définir des données statiques en entrée d’un modèle et spécifier ce qui est attendu en sortie.
Cela est très puissant car ces tests vont pouvoir nous assurer que la logique de nos modèles est bien respectée.
WITH products AS (
SELECT
products.name,
TRIM(SPLIT(products.name, '-')[0]) AS brand
FROM {{ ref('stg_ecommerce__products') }} products
)
SELECT * FROM products
Par exemple, ce modèle ci-dessus va permettre d’extraire la marque du produit en se basant sur son nom. La première partie du nom du produit correspond à la marque. Nous pouvons mettre en place un test unitaire afin de s’assurer que cette logique fonctionne bien.
unit_tests:
- name: test_brand
description: "Vérifier la déduction de la marque"
model: products
given:
- input: ref('stg_ecommerce__products')
rows:
- {name: 'Adidas - Chaussure Forum Low'}
- {name: 'Nike - Club Sweatshirt'}
expect:
rows:
- {name: 'Adidas - Chaussure Forum Low', brand: 'Adidas'}
- {name: 'Nike - Club Sweatshirt', brand: 'Nike'}
La logique de ces tests unitaires se base sur le principe de spécifier les données en entrée et ce qui est attendu comme résultat.
Ici, c’est un test très basique pour une logique qui n’en a pas vraiment besoin. Dans un prochain article, je vous détaillerai les nombreuses possibilités qu’offre cette nouvelle fonctionnalité apparue dans la version 1.8 de dbt !
Conseils pour les tests dbt
Lors de la mise en place de ces tests dbt, il est essentiel d’avoir certains points en tête afin que votre stratégie de tests soit efficace et qu’elle ne fasse pas flamber votre facture à la fin du mois.
En effet, si votre data warehouse vous facture à chaque requête en fonction de la quantité de données traitées, il faut bien prendre conscience que tous ces tests dbt que vous mettez en place deviendront des requêtes supplémentaires qui seront exécutées. Si vous êtes dans cette situation, viser un test coverage de 100% n’est peut-être pas un but à atteindre…
Tester, c’est bien, mais visualiser facilement les résultats de vos tests dbt, c’est encore mieux. Pour cela, plusieurs options s’offrent à vous :
- Stocker les erreurs de vos tests. Pour cela, il suffit de spécifier la configuration
store_failures
à un test. Si cette configuration est définie àtrue
, cela va créer une table pour ce test contenant les différentes erreurs remontées. Vous pourrez ainsi exploiter cette table afin de mettre en place un dashboard de monitoring par exemple.
version: 2
models:
- name: customers
columns:
- name: customer_id
tests:
- unique:
config:
store_failures: true
- Utiliser le packagedbt_artifacts pour stocker les résultats des tests et construire un dashboard qui permet de les visualiser. Là ppour le coup, il n’y a que le plugin à installer et les metadata viendront s’alimenter à chaque execution de vos modèles.
Enfin, une dernière chose à avoir en tête lorsque nous mettons en place nos tests, c’est l’orchestration de nos modèles. Par défaut, dbt ne va pas exécuter les modèles qui dépendent d’un modèle qui a été en erreur. Ça peut être le comportement que l’on souhaite avoir, cela permet de ne pas construire pour rien des modèles qui sont erronés. Mais j’ai eu de nombreuses fois la demande de quand même exécuter les modèles afin de quand même avoir des données dans les tables. Pour cela, il y a deux possibilités :
- Définir la sévérité du test en
warn
. L’erreur sera remontée mais ne sera pas bloquante pour la suite de la chaîne d’exécution.
version: 2
models:
- name: customers
columns:
- name: customer_id
tests:
- unique:
config:
severity: warn
Utiliser un orchestrateur plus fin que celui de base dans dbt (Dagster, Airflow, …). Cela permettra de définir précisément le comportement que l’on souhaite avoir en cas d’erreur.
Il est possible de raconter encore énormément de choses sur les tests dbt (et j’ai d’ailleurs prévu de le faire sur ce blog !) mais on a fait un premier tour de ce qu’il est possible de mettre en place dans dbt afin d’améliorer sa Data Quality. J’espère que cet article vous aide à y voir plus clair.
On se retrouve bientôt pour de nouveaux articles !