Un pipeline data sans tests est une bombe à retardement. En entretien Senior, on évalue la capacité à écrire des tests automatisés rigoureux pour les pipelines de données.
Pourquoi pytest est-il préféré à unittest pour les pipelines data ?
import pytest
import pandas as pd
from my_pipeline.transforms import calculate_revenue
# Test unitaire simple
def test_calculate_revenue_basic():
df = pd.DataFrame({
'quantity': [1, 2, 3],
'unit_price': [10.0, 5.0, 8.0]
})
result = calculate_revenue(df)
assert list(result['revenue']) == [10.0, 10.0, 24.0]
# Test des cas limites
def test_calculate_revenue_zero_quantity():
df = pd.DataFrame({'quantity': [0], 'unit_price': [10.0]})
result = calculate_revenue(df)
assert result['revenue'].iloc[0] == 0.0
# Test des valeurs nulles
def test_calculate_revenue_null_handling():
df = pd.DataFrame({'quantity': [None, 1], 'unit_price': [10.0, 5.0]})
result = calculate_revenue(df)
assert result['revenue'].isna().sum() == 1Comment utilisez-vous les fixtures pytest pour vos tests data ?
import pytest
import pandas as pd
from sqlalchemy import create_engine
@pytest.fixture
def sample_orders():
return pd.DataFrame({
'order_id': ['O001', 'O002', 'O003'],
'customer_id': ['C1', 'C1', 'C2'],
'amount': [100.0, 50.0, 200.0],
'status': ['completed', 'cancelled', 'completed'],
'order_date': pd.to_datetime(['2024-01-01', '2024-01-02', '2024-01-03'])
})
@pytest.fixture(scope='session')
def test_db():
# Base de données SQLite en mémoire pour les tests
engine = create_engine('sqlite:///:memory:')
yield engine
engine.dispose()
def test_revenue_by_customer(sample_orders):
result = compute_revenue(sample_orders)
assert result.loc[result['customer_id'] == 'C1', 'revenue'].iloc[0] == 100.0Comment testez-vous du code qui appelle une base de données ou une API externe ?
from unittest.mock import patch, MagicMock
import pytest
def test_extract_from_api():
with patch('my_pipeline.extract.requests.get') as mock_get:
mock_get.return_value.json.return_value = [
{'id': 1, 'amount': 100},
{'id': 2, 'amount': 200}
]
mock_get.return_value.status_code = 200
result = extract_from_api('https://api.exemple.com/orders')
assert len(result) == 2
assert result[0]['amount'] == 100
mock_get.assert_called_once_with('https://api.exemple.com/orders',
timeout=10)
# pytest-mock : syntaxe plus propre
def test_with_mocker(mocker):
mock_conn = mocker.patch('my_pipeline.db.get_connection')
mock_conn.return_value.execute.return_value.fetchdf.return_value = pd.DataFrame({'id': [1]})
result = load_from_db('SELECT * FROM orders')
assert len(result) == 1Comment testez-vous une transformation SQL dbt ou une vue Snowflake ?
# Tester une transformation SQL avec DuckDB (rapide, en mémoire)
import duckdb
import pytest
def test_revenue_aggregation():
conn = duckdb.connect()
# Créer les données de test en mémoire
conn.execute("""
CREATE TABLE orders AS
SELECT * FROM (VALUES
('O1', 'C1', 100.0, 'completed'),
('O2', 'C1', 50.0, 'cancelled'),
('O3', 'C2', 200.0, 'completed')
) t(order_id, customer_id, amount, status)
""")
# Tester la transformation
result = conn.execute("""
SELECT customer_id, SUM(amount) as revenue
FROM orders
WHERE status = 'completed'
GROUP BY customer_id
""").fetchdf()
assert result.loc[result['customer_id'] == 'C1', 'revenue'].iloc[0] == 100.0
assert result.loc[result['customer_id'] == 'C2', 'revenue'].iloc[0] == 200.0Comment allez-vous au-delà des tests YAML dbt avec pytest ?
# Singular test dbt : test qui ne doit pas retourner de lignes
-- tests/orders_no_negative_amount.sql
SELECT order_id, amount
FROM {{ ref('fct_orders') }}
WHERE amount < 0Comment testez-vous un pipeline entier de l ingestion à la transformation ?
| Niveau | Maitrise | Signal GO | NO-GO |
|---|---|---|---|
| Confirmé | Tests unitaires pytest, fixtures, mocks basiques | A écrit des tests pour ses fonctions de transformation, utilise des fixtures | N a aucun test dans ses projets |
| Senior | Tests SQL avec DuckDB, tests d intégration, CI automatisée | Teste ses transformations SQL avec DuckDB, a une CI qui lance les tests | Pense que les tests dbt YAML suffisent pour tout |
Premier entretien gratuit. Rapport GO/NO-GO sous 48h.