0Pricing
Pandas & NumPy Academy · Leçon

Tester les étapes du pipeline avec des assertions

Ajoutez à chaque étape des vérifications du nombre de lignes, des assertions sur les valeurs nulles et des garde-fous sur les colonnes attendues afin que les erreurs apparaissent immédiatement.

Tester les étapes du pipeline avec des assertions est une leçon Pandas & NumPy Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Pandas & NumPy Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Pandas & NumPy Academy comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

Why Test Pipeline Steps?

A data pipeline that runs without errors can still produce silently wrong output: rows dropped by the wrong filter, a column multiplied by the wrong factor, or a merge that duplicates rows because the join key was not unique. The only way to catch these silent failures is to embed assertions that verify expected properties of the data at each pipeline stage. Assertions turn logical bugs into loud, immediately visible errors.

import pandas as pd
import numpy as np

# A transform that looks correct but has a bug:
def compute_revenue_buggy(df):
    # BUG: unit_price should be multiplied, not added
    df['revenue'] = df['quantity'] + df['unit_price']
    return df

# Without assertions, this runs silently with wrong numbers.

Row Count Checks

After every filtering step, assert that the resulting row count is within an expected range. Too few rows means the filter was too aggressive; too many rows means a join duplicated records. Express the expected range as a fraction of the input: for example, a null-drop step should never remove more than 30 % of rows in healthy data. This guard catches unexpected data quality changes in upstream sources.

def drop_nulls_guarded(df, required_cols, max_drop_fraction=0.3):
    before = len(df)
    result = df.dropna(subset=required_cols)
    after = len(result)
    drop_fraction = (before - after) / before
    assert drop_fraction <= max_drop_fraction, \
        f'dropna removed {drop_fraction:.1%} of rows (limit {max_drop_fraction:.1%})'
    return result

Column Existence Checks

Assert that all expected output columns exist after every transformation function. If a rename step accidentally uses the wrong key, the resulting column is missing — and the error only surfaces much later when another step tries to use it. An assertion immediately after the transform catches the problem at the source rather than three steps later with a confusing KeyError.

def compute_revenue(df):
    df = df.assign(revenue=lambda d: d['quantity'] * d['unit_price'])

    # Guard: check that the new column exists and is non-null
    assert 'revenue' in df.columns, 'revenue column not created'
    assert df['revenue'].notna().all(), 'revenue has unexpected NaNs'
    return df

Value Range Assertions

After computing a revenue column, assert it is non-negative. After parsing dates, assert they fall within the expected year range. After encoding categories, assert no unexpected codes appear. Each assertion is a data contract that documents expected behaviour and catches violations early. Write them as assertions rather than print statements so they raise errors in automated pipeline runs.

def validate_computed_columns(df):
    assert (df['revenue'] >= 0).all(), \
        f'Negative revenue: {df[df["revenue"] < 0]["revenue"].head().tolist()}'
    assert (df['quantity'] > 0).all(), \
        'Non-positive quantity found'
    assert df['revenue'].between(0, 1_000_000).all(), \
        'Revenue out of plausible range'
    print('Value range checks passed.')

No-Duplication Guards After Merges

A common data bug is a many-to-many merge that accidentally multiplies rows. After every pd.merge(), assert that the row count is expected — typically that it did not exceed the left DataFrame's row count for a left join. Also assert that the key column is unique if it should be, so you catch accidental cross-joins immediately.

def safe_merge(left, right, on, how='left'):
    before = len(left)
    result = left.merge(right, on=on, how=how)
    after = len(result)

    if how == 'left':
        assert after == before, \
            f'Left join increased rows from {before} to {after} — check key uniqueness in right df'
    return result

Null Assertion After Critical Steps

Certain columns must never be null at any point in the pipeline. Assert df['key_col'].notna().all() after every step that could accidentally introduce nulls — such as a merge that fails to match some rows (producing NaN in joined columns), or a map() call that returns NaN for unmapped values. Make these assertions the last two lines of every transformation function that touches those columns.

NOT_NULL_AFTER_TRANSFORM = ['order_id', 'revenue', 'category']

def post_transform_checks(df):
    for col in NOT_NULL_AFTER_TRANSFORM:
        null_count = df[col].isna().sum()
        assert null_count == 0, \
            f'{col}: {null_count} unexpected NaN values after transform'
    print('Null checks passed.')
    return df

Building a Test Suite for Pipeline Steps

Write unit tests for each pipeline function using tiny, hand-crafted DataFrames that isolate the logic being tested. Each test should: set up a minimal input, call the function, and assert the output properties. Using Python's built-in assert is sufficient for simple pipelines; for larger projects, use pytest to run all tests automatically before deploying.

def test_compute_revenue():
    test_df = pd.DataFrame({
        'quantity': [2, 3],
        'unit_price': [10.0, 5.0]
    })
    result = compute_revenue(test_df)

    assert 'revenue' in result.columns
    assert result['revenue'].tolist() == [20.0, 15.0]
    assert result['revenue'].dtype == float
    print('test_compute_revenue PASSED')

test_compute_revenue()

Testing Edge Cases

Good tests cover not just the happy path but also edge cases: all-null input, empty DataFrame, single-row DataFrame, and columns with extreme values. An empty DataFrame should return an empty DataFrame, not an error. A DataFrame with a single row should compute the correct result. Test these cases explicitly to ensure the pipeline handles them gracefully in production when unusual data arrives.

def test_empty_df():
    empty = pd.DataFrame({'quantity': [], 'unit_price': []})
    result = compute_revenue(empty)
    assert len(result) == 0, 'Empty input should produce empty output'
    assert 'revenue' in result.columns, 'Revenue column should still be created'
    print('test_empty_df PASSED')

def test_single_row():
    single = pd.DataFrame({'quantity': [1], 'unit_price': [99.0]})
    result = compute_revenue(single)
    assert result['revenue'].iloc[0] == 99.0
    print('test_single_row PASSED')

test_empty_df()
test_single_row()

Integration Test: Full Pipeline on Sample Data

Beyond unit tests on individual functions, write an integration test that runs the complete pipeline on a small representative sample of real data. Assert that the output has the expected number of columns, the key column is unique, and the total revenue is within a plausible range. This end-to-end check catches bugs in the interaction between steps that unit tests do not reveal.

def integration_test(config):
    raw = extract(config)
    clean = transform(raw, config)

    assert set(config['required_cols']).issubset(set(clean.columns))
    assert clean['order_id'].is_unique
    assert (clean['revenue'] >= 0).all()
    assert len(clean) > 0

    print(f'Integration test PASSED. Output: {clean.shape}')

integration_test(CONFIG)

Continuous Testing with pytest

As the pipeline grows, collect all tests into a tests/ directory and run them with pytest from the command line. A conftest.py file can create shared fixtures like sample DataFrames. Integrate pytest into your CI/CD pipeline so every code change automatically runs all tests before deployment. A failing test blocks the deployment, preventing broken code from reaching production.

# tests/test_transform.py — example structure
# import pytest, pandas as pd
# from pipeline.transform import compute_revenue, drop_nulls

# @pytest.fixture
# def sample_df():
#     return pd.DataFrame({'quantity': [2, 3], 'unit_price': [10.0, 5.0]})

# def test_revenue(sample_df):
#     result = compute_revenue(sample_df)
#     assert result['revenue'].tolist() == [20.0, 15.0]

print('Run: pytest tests/ -v  to execute all pipeline tests')

Assertions as Living Documentation

Each assertion in the pipeline is both a guard and a piece of documentation: it states, in machine-readable form, what the data must look like at that point. When a new analyst joins the project and reads the pipeline code, the assertions tell them the data contracts without requiring a separate specification document. Treat every assertion as you would a comment — make the message descriptive enough to understand the business rule it enforces.

# These assertions document business rules as code:
assert (df['order_date'] >= pd.Timestamp('2020-01-01')).all(), \
    'Company was founded 2020-01-01; no orders can predate this'
assert df['region'].isin({'North', 'South', 'East', 'West', 'Central'}).all(), \
    'Only 5 sales regions are valid; new regions require config update'
print('Business rules verified as assertions.')

Quick Check

Test your understanding of Data Analysis concepts from this lesson.

Lesson Recap

In this lesson you learned: embedding row count, column existence, value range, and null checks as in-pipeline assertions, writing unit tests for individual transformation functions with edge-case coverage, and running integration tests and treating assertions as living data contract documentation. Next up we explore scheduling and logging pipeline runs for automated daily execution.

Questions Fréquemment Posées

La leçon « Tester les étapes du pipeline avec des assertions » est-elle gratuite ?

Oui — le texte complet de « Tester les étapes du pipeline avec des assertions » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Pandas & NumPy Academy, passe à CoddyKit PRO. Le cours Pandas & NumPy Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Tester les étapes du pipeline avec des assertions » ?

Ajoutez à chaque étape des vérifications du nombre de lignes, des assertions sur les valeurs nulles et des garde-fous sur les colonnes attendues afin que les erreurs apparaissent immédiatement. Tu pratiques Pandas & NumPy Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Pandas & NumPy Academy ?

Aucune expérience préalable n'est requise. Pandas & NumPy Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Tester les étapes du pipeline avec des assertions » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Pandas & NumPy Academy ?

Oui. Chaque leçon Pandas & NumPy Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Structurer les étapes de transformation en fonctions
  2. Paramétrer les pipelines avec des dictionnaires de configuration
  3. Tester les étapes du pipeline avec des assertions
  4. Planifier et journaliser les exécutions du pipeline
← Retour à Pandas & NumPy Academy