Cette note couvre les patterns pour exécuter les tests unitaires dbt en CI sur BigQuery. Une configuration CI naïve gaspille de l’argent et crée des conflits entre les exécutions concurrentes de PRs ; les patterns présentés ici adressent les deux problèmes.
La stratégie CI/CD globale couvre le tableau complet (Slim CI, diffing de données, linting). Cette note se concentre spécifiquement sur le workflow des tests unitaires.
Le pattern de base
Un workflow GitHub Actions prêt pour la production pour les tests unitaires BigQuery :
name: dbt CI
on: pull_request: branches: [main]
env: DBT_PROFILES_DIR: ./ GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GCP_SA_KEY_PATH }}
jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11"
- name: Install dbt run: pip install dbt-bigquery
- name: Set up GCP credentials run: echo '${{ secrets.GCP_SA_KEY }}' > /tmp/gcp-key.json env: GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
- name: Create CI dataset name run: | echo "CI_DATASET=ci_$(date +'%Y%m%d_%H%M%S')_${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Build upstream models (empty) run: | dbt run --select +test_type:unit --empty --target ci env: CI_DATASET: ${{ env.CI_DATASET }}
- name: Run unit tests run: | dbt test --select test_type:unit --target ci env: CI_DATASET: ${{ env.CI_DATASET }}
- name: Cleanup CI dataset if: always() run: | bq rm -r -f ${{ env.CI_DATASET }}Quatre décisions de conception rendent ce workflow prêt pour la production.
Dataset unique par exécution CI
CI_DATASET=ci_$(date +'%Y%m%d_%H%M%S')_${GITHUB_SHA::7}Chaque exécution CI crée son propre dataset BigQuery en utilisant un timestamp et le SHA du commit. Cela évite les conflits lorsque plusieurs PRs exécutent le CI en concurrence — un problème courant lorsque l’équipe est active et que les PRs s’accumulent.
Sans datasets uniques, les exécutions CI concurrentes écrivent dans les mêmes tables et interfèrent les unes avec les autres. Le test A construit un modèle, le test B l’écrase avec des données différentes, le test unitaire de A échoue parce que le schéma a changé. Les datasets uniques éliminent complètement ce problème.
Le flag --target ci dans dbt pointe vers une cible profiles.yml qui utilise la variable d’environnement CI_DATASET comme nom de schéma. Votre profiles.yml a besoin d’une entrée correspondante :
my_project: target: dev outputs: ci: type: bigquery method: service-account project: my-gcp-project dataset: "{{ env_var('CI_DATASET') }}" threads: 4 keyfile: /tmp/gcp-key.jsonLe flag —empty
dbt run --select +test_type:unit --empty --target ciC’est la plus grande optimisation de coût. Le flag --empty crée des tables avec les schémas corrects mais zéro ligne. Les tests unitaires n’ont pas besoin de données amont réelles — ils utilisent leurs propres entrées mockées. Ils ont juste besoin que les tables amont existent pour que le SQL compile.
Sans --empty, vous devriez soit :
- Construire tous les modèles amont avec des données réelles (coûteux, lent)
- Maintenir un dataset CI séparé avec des tables pré-construites (charge de maintenance)
Avec --empty, l’étape de build se termine en quelques secondes et consomme un minimum de slots BigQuery.
Nettoyage toujours exécuté
- name: Cleanup CI dataset if: always() run: | bq rm -r -f ${{ env.CI_DATASET }}Le if: always() garantit que le nettoyage s’exécute même lorsque les tests échouent. Sans ça, les exécutions CI en échec laissent des datasets orphelins dans BigQuery, et vous vous retrouvez avec des dizaines de datasets ci_20260315_* qui encombrent votre projet.
Le flag -r supprime le dataset de manière récursive (y compris toutes les tables), et -f force la suppression sans confirmation.
Séparer les tests unitaires des tests de données
Le workflow exécute dbt test --select test_type:unit — pas dbt test. C’est délibéré. Les tests unitaires et les tests de données servent des objectifs différents et s’exécutent dans des contextes différents :
- Tests unitaires : s’exécutent en CI sur chaque PR. Ils utilisent des données mockées. Ils vérifient la logique.
- Tests de données : s’exécutent en production après la construction des modèles. Ils utilisent des données réelles. Ils vérifient la santé des données.
Exécuter des tests de données en CI contre un dataset vide n’a aucun sens — il n’y a pas de données à valider. Gardez les deux séparés.
Exclure les tests unitaires de la production
L’autre face de la médaille : les tests unitaires ne doivent jamais s’exécuter en production. Ils utilisent des données mockées et n’ont aucune valeur là-bas. Excluez-les des builds de production :
# Dans votre script de déploiement en productiondbt build --exclude-resource-type unit_testOu définissez-le comme variable d’environnement dans votre environnement de production :
export DBT_EXCLUDE_RESOURCE_TYPES=unit_testdbt buildCela crée une séparation nette : les tests unitaires bloquent les déploiements en CI, les tests de données surveillent la santé en production. Aucun ne s’exécute là où il ne devrait pas.
Considérations de coût pour BigQuery
Même avec --empty, les exécutions CI sur BigQuery ne sont pas gratuites. Chaque test unitaire exécute une vraie requête. Pour les équipes avec des suites de tests importantes, quelques optimisations supplémentaires aident :
-
Utilisez une réservation CI dédiée avec un minimum de slots. Les requêtes de tests unitaires sont légères — elles n’ont pas besoin de la même capacité en slots que les charges de travail de production. Une petite réservation (50-100 slots) gère les exécutions CI sans concurrencer la production.
-
Mettez en cache le build
--empty. Si vos schémas amont ne changent pas souvent, vous pouvez sauter l’étape de build sur les PRs qui ne modifient pas les modèles amont. Utilisezstate:modified+pour reconstruire sélectivement uniquement ce qui a changé. -
Étiquetez les tests par priorité. Exécutez les tests unitaires
tag:criticalsur chaque PR et la suite complète lors des merges vers main. Cela maintient des retours rapides sur les PRs tout en attrapant les problèmes avant la mise en production. -
Surveillez les coûts des datasets CI. Requêtez
INFORMATION_SCHEMA.JOBSfiltré par le compte de service CI pour suivre le coût de vos exécutions CI de tests unitaires. Si ça croît plus vite que votre suite de tests, quelque chose est inefficace.
Au-delà de GitHub Actions
Les patterns présentés ici — datasets uniques, builds --empty, exécutions séparées tests unitaires/données, nettoyage systématique — s’appliquent à tout système CI. GitLab CI, CircleCI et Cloud Build supportent tous la même structure de workflow. Les éléments spécifiques à BigQuery (bq rm, auth par compte de service, nommage des datasets) restent les mêmes quelle que soit la plateforme CI.
Pour les équipes utilisant dbt Cloud, le job CI intégré gère certains de ces aspects automatiquement. Pour un contrôle fin de l’exécution des tests unitaires — jobs séparés pour les tests unitaires vs données, nommage personnalisé des datasets, sélection de tests par priorité — un workflow personnalisé est nécessaire.