AccueilBlogTest technique Python testing avancé : property-based testing, mutation testing
Guide recrutement data

Test technique Python testing avancé : property-based testing, mutation testing

Les tests classiques vérifient des exemples. Le property-based testing vérifie des propriétés sur des milliers d exemples générés. En entretien Expert, on évalue ces techniques avancées.

Data Builder·Juin 2025·6 min de lecture·Data Engineer
Sommaire
  1. Property-based testing avec Hypothesis
  2. Définir les bonnes propriétés
  3. Mutation testing
  4. Paramétrisation avancée pytest
  5. Tests de performance
  6. Coverage avancé
  7. Grille

1Hypothesis : trouver les cas limites automatiquement

Question discriminante

Qu est-ce que le property-based testing ? En quoi est-il supérieur aux tests d exemples ?

from hypothesis import given, strategies as st import pandas as pd # Test classique : vérifie 1 exemple def test_revenue_calculation_example(): df = pd.DataFrame({'qty': [2], 'price': [10.0]}) assert calculate_revenue(df)['revenue'].iloc[0] == 20.0 # Property-based : Hypothesis génère des milliers d exemples @given( qty=st.integers(min_value=0, max_value=10000), price=st.floats(min_value=0.01, max_value=100000, allow_nan=False) ) def test_revenue_is_always_non_negative(qty, price): df = pd.DataFrame({'qty': [qty], 'price': [price]}) result = calculate_revenue(df) assert result['revenue'].iloc[0] >= 0 # Hypothesis trouve automatiquement les cas qui échouent # et les minimise (shrinking) pour donner le plus petit contre-exemple @given(st.data()) def test_partition_total_is_preserved(data): df = data.draw(st.data_frames( columns=[st.column('amount', elements=st.floats(0, 1000, allow_nan=False))] )) result = transform_by_region(df) assert abs(result['amount'].sum() - df['amount'].sum()) < 0.01

2Définir les bonnes propriétés

Question discriminante

Comment identifiez-vous les propriétés à tester sur un pipeline de données ?

3Mutation testing avec Mutmut

Question discriminante

Qu est-ce que le mutation testing ? Comment l utilisez-vous pour évaluer la qualité des tests ?

## Mutation testing : vérifier que vos tests détectent les bugs ## Installation pip install mutmut ## Lancer le mutation testing mutmut run --paths-to-mutate=src/transforms.py ## Résultats mutmut results # 45 mutations tested # 38 killed (tests ont détecté la mutation) # 7 survived (tests n ont PAS détecté la mutation = lacune) mutmut show 42 # voir la mutation qui a survécu # Original : if amount > 0: # Mutant : if amount >= 0: # -> Ce test manque ! Ajouter test avec amount=0 ## Interpréter le score # Mutation score = killed / total # > 80% : bonne couverture # > 90% : excellente couverture

4Paramétrisation avancée pytest

Question discriminante

Comment utilisez-vous pytest.mark.parametrize pour tester des pipelines data ?

import pytest from datetime import date # Paramétrisation simple @pytest.mark.parametrize('status,expected_count', [ ('completed', 10), ('cancelled', 3), ('pending', 7) ]) def test_filter_by_status(status, expected_count, sample_orders): result = filter_orders(sample_orders, status=status) assert len(result) == expected_count # Paramétrisation avec IDs @pytest.mark.parametrize('date_range,expected', [ pytest.param((date(2025,1,1), date(2025,1,31)), 100, id='january'), pytest.param((date(2025,2,1), date(2025,2,28)), 85, id='february'), ], ids=lambda x: x if isinstance(x, str) else None) def test_revenue_by_month(date_range, expected): result = get_revenue(*date_range) assert abs(result - expected) < 5 # Fixtures paramétrées @pytest.fixture(params=['bigquery', 'snowflake', 'duckdb']) def warehouse(request): return create_warehouse_connection(request.param)

5Tests de performance avec pytest-benchmark

Question discriminante

Comment mesurez-vous et suivez-vous les performances de vos transformations ?

## pytest-benchmark : mesurer les performances de manière reproductible import pytest def test_transform_performance(benchmark, sample_df_100k): # benchmark.pedantic : contrôle précis des itérations result = benchmark.pedantic( transform_orders, args=(sample_df_100k,), rounds=10, iterations=3 ) assert len(result) > 0 ## Historique des benchmarks # pytest --benchmark-save=baseline # pytest --benchmark-compare=baseline --benchmark-compare-fail=mean:10% # -> Échoue si la performance se dégrade de plus de 10% ## Intégré dans CI pour détecter les régressions de performance ## avant de merger en production

6Coverage avancé : branches et conditions

Question discriminante

Comment allez-vous au-delà du simple coverage de lignes ?

## pytest.ini [pytest] addopts = --cov=src --cov-report=term-missing --cov-branch ## Branch coverage : vérifie que les deux branches d un if sont testées ## if amount > 0: -> tester amount > 0 ET amount <= 0 ## coverage.ini [coverage:run] branch = True omit = tests/* [coverage:report] exclude_lines = pragma: no cover # annoter les lignes à exclure if __name__ == .__main__. raise NotImplementedError fail_under = 85 # échoue si coverage < 85% ## Voir les branches non couvertes coverage html # Ouvrir htmlcov/index.html : lignes et branches en rouge

7Grille par niveau

NiveauMaitriseSignal GONO-GO
SeniorHypothesis, paramétrisation avancée, branch coverageA utilisé Hypothesis pour découvrir des bugs, configure le branch coverageNe sait pas ce qu est le property-based testing
ExpertMutation testing, benchmarks, stratégies Hypothesis customA utilisé mutmut pour trouver des lacunes dans ses tests, a des benchmarks en CIN a jamais entendu parler du mutation testing

Vous recrutez un Data Engineer rigoureux ?

Premier entretien gratuit. Rapport GO/NO-GO sous 48h.