ServicesÀ proposNotesContact Me contacter →
EN FR
Note

Tests unitaires de l'extraction de chaînes dans dbt

Comment tester unitairement la logique regex et la manipulation de chaînes dans dbt — documentation des cas limites, gestion gracieuse des échecs, et protection contre les régressions pour un parsing fragile.

Planté
dbttesting

La logique d’extraction de chaînes est notoirement fragile. Les patterns regex qui fonctionnent pour vos données typiques échouent souvent sur les cas limites — domaines inhabituels, caractères encodés, délimiteurs manquants, entrées nulles. Les tests unitaires sont essentiels pour documenter le comportement attendu et détecter les régressions quand quelqu’un “simplifie” un regex.

Le pattern de documentation des cas limites

L’objectif est de créer une ligne de test par catégorie d’entrée, couvrant non seulement les cas normaux mais toutes les façons dont l’entrée peut être inhabituelle :

unit_tests:
- name: test_mrt_core_customers_email_domain_extraction
model: mrt__core__customers
description: "L'extraction de domaine email gère différents formats"
given:
- input: ref('base__crm__customers')
rows:
- {customer_id: 1, email: "alice@example.com"}
- {customer_id: 2, email: "bob.smith@subdomain.company.co.uk"}
- {customer_id: 3, email: "MAJUSCULES@DOMAINE.COM"}
- {customer_id: 4, email: "email-invalide"}
- {customer_id: 5, email: null}
expect:
rows:
- {customer_id: 1, email_domain: "example.com"}
- {customer_id: 2, email_domain: "subdomain.company.co.uk"}
- {customer_id: 3, email_domain: "domaine.com"}
- {customer_id: 4, email_domain: null}
- {customer_id: 5, email_domain: null}

Chaque ligne de test a une fonction spécifique :

  • Cas simple (alice@example.com) : comportement de base. Si cela échoue, quelque chose est fondamentalement cassé.
  • Domaine complexe (subdomain.company.co.uk) : plusieurs points, TLD multi-parties. Un regex qui coupe au premier point après @ ne retournerait que “subdomain” ici.
  • Gestion de la casse (MAJUSCULES@DOMAINE.COM) : la sortie devrait-elle normaliser en minuscules ? La sortie attendue dit oui. Si votre modèle ne met pas le domaine en minuscules, cette ligne documente cette décision.
  • Entrée invalide (email-invalide) : pas de symbole @. L’attente est null plutôt qu’une erreur. Cela protège contre les échecs de regex qui lèvent des exceptions au lieu de retourner gracieusement.
  • Entrée nulle : gestion explicite des nulls. REGEXP_EXTRACT(null, ...) devrait retourner null, pas planter.

Pourquoi les tests de chaînes préviennent les régressions

La vraie valeur de ces tests apparaît lors de la maintenance. Considérez ce qui se passe sans eux :

  1. Quelqu’un remarque que l’extraction de domaine est “trop compliquée” et simplifie le regex
  2. La version simplifiée fonctionne pour les emails standard comme alice@example.com
  3. Elle casse pour les sous-domaines, ou arrête de mettre en minuscules, ou plante sur les entrées nulles
  4. Le changement passe la revue de code parce que le reviewer ne teste mentalement qu’avec des entrées simples
  5. Les données en production incluent tous les cas limites, et les rapports en aval se cassent

Avec le test unitaire en place, le regex simplifié échoue sur les lignes 2, 3, 4 ou 5 pendant le CI. Le développeur voit exactement quels cas limites son changement a cassés et peut décider si c’est acceptable.

Scénarios courants d’extraction de chaînes

Au-delà des domaines email, le même pattern s’applique à :

Extraction de paramètres UTM : testez avec des caractères encodés (%20), des paramètres manquants, des variations d’ordre de paramètres, des noms de paramètres en majuscules, et des URLs sans query string.

Normalisation des numéros de téléphone : testez avec des formats internationaux (+33 6 12 34 56 78), des parenthèses ((555) 123-4567), des extensions (x1234), et des chaînes qui ressemblent à des numéros de téléphone mais n’en sont pas.

Parsing de chemins d’URL : testez avec des barres obliques finales, des query strings, des fragments, des espaces encodés, et des URLs relatives vs. absolues.

Découpage de noms : testez avec des noms à trait d’union, des préfixes (Dr., Mme), des suffixes (Jr., III), des noms uniques, et des noms avec des caractères spéciaux.

Dans chaque cas, le pattern est le même : une ligne par catégorie d’entrée, progressant du simple à l’inhabituel au cassé. Le test documente ce que “comportement correct” signifie pour chaque catégorie. Les futurs développeurs peuvent lire la fixture de test pour comprendre le comportement attendu sans avoir à décortiquer des patterns regex.

Gardez les lignes de test intentionnelles

Évitez la tentation d’ajouter des dizaines de lignes de test similaires. Cinq à sept entrées bien choisies qui testent chacune un aspect distinct de la logique vaut mieux que vingt variations du même cas normal. Chaque ligne devrait tester quelque chose que les lignes précédentes ne testent pas. Si vous ne pouvez pas expliquer quel nouveau scénario une ligne couvre, elle est redondante.

Le bon modèle mental : chaque ligne est un test de régression pour un cas limite spécifique que vous avez vu en production ou que vous anticipez. Quand vous corrigez un bug en production causé par un format d’entrée inattendu, ajoutez cette entrée exacte comme nouvelle ligne de test. Avec le temps, votre fixture de test devient un document vivant de tous les cas limites que votre logique de parsing a rencontrés.