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.
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.01Comment identifiez-vous les propriétés à tester sur un pipeline de données ?
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 couvertureComment 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)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 productionComment 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| Niveau | Maitrise | Signal GO | NO-GO |
|---|---|---|---|
| Senior | Hypothesis, paramétrisation avancée, branch coverage | A utilisé Hypothesis pour découvrir des bugs, configure le branch coverage | Ne sait pas ce qu est le property-based testing |
| Expert | Mutation testing, benchmarks, stratégies Hypothesis custom | A utilisé mutmut pour trouver des lacunes dans ses tests, a des benchmarks en CI | N a jamais entendu parler du mutation testing |
Premier entretien gratuit. Rapport GO/NO-GO sous 48h.