0Pricing
Pandas & NumPy Academy · Lezione

Testare i passaggi della pipeline con asserzioni

Aggiunga a ogni fase controlli sul numero di righe, asserzioni sui valori nulli e verifiche sulle colonne attese, in modo che gli errori emergano immediatamente.

Testare i passaggi della pipeline con asserzioni è una lezione Pandas & NumPy Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Pandas & NumPy Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Pandas & NumPy Academy include 4 lezioni in totale.

Perché testare i passaggi della pipeline?

Una pipeline di dati che viene eseguita senza errori può comunque produrre un output errato senza segnalarlo: righe eliminate da un filtro sbagliato, una colonna moltiplicata per il fattore errato oppure un merge che duplica le righe perché la chiave di join non era univoca. L'unico modo per individuare questi errori silenziosi consiste nell'inserire asserzioni che verifichino le proprietà attese dei dati in ogni fase della pipeline. Le asserzioni trasformano gli errori logici in errori evidenti e immediatamente visibili.

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.

Controlli sul numero di righe

Dopo ogni passaggio di filtraggio, verifichi con un'asserzione che il numero di righe risultante rientri nell'intervallo previsto. Un numero troppo basso indica che il filtro era eccessivamente restrittivo; un numero troppo alto indica che un join ha duplicato dei record. Esprima l'intervallo previsto come frazione dell'input: per esempio, in dati integri un passaggio che elimina i valori nulli non dovrebbe mai rimuovere più del 30 % delle righe. Questa protezione individua cambiamenti imprevisti nella qualità dei dati sorgente.

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

Controlli sull'esistenza delle colonne

Verifichi con un'asserzione che tutte le colonne di output previste esistano dopo ogni funzione di trasformazione. Se un passaggio di rinomina usa accidentalmente la chiave sbagliata, la colonna risultante sarà assente e l'errore emergerà solo molto più avanti, quando un altro passaggio tenterà di utilizzarla. Un'asserzione subito dopo la trasformazione individua il problema alla sua origine, invece di farlo emergere tre passaggi dopo con un KeyError poco chiaro.

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

Asserzioni sull'intervallo dei valori

Dopo aver calcolato una colonna dei ricavi, verifichi con un'asserzione che non contenga valori negativi. Dopo aver analizzato le date, verifichi che rientrino nell'intervallo di anni previsto. Dopo aver codificato le categorie, verifichi che non compaiano codici imprevisti. Ogni asserzione è un contratto sui dati che documenta il comportamento previsto e individua tempestivamente le violazioni. Le scriva come asserzioni anziché come istruzioni di stampa, in modo che generino errori nelle esecuzioni automatizzate della pipeline.

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.')

Protezioni contro le duplicazioni dopo i merge

Un errore comune nei dati è un merge molti-a-molti che moltiplica accidentalmente le righe. Dopo ogni pd.merge(), verifichi con un'asserzione che il numero di righe sia quello previsto: in genere, per un left join, non dovrebbe superare il numero di righe del DataFrame a sinistra. Verifichi inoltre che la colonna chiave sia univoca, se dovrebbe esserlo, così da individuare immediatamente i cross join accidentali.

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

Asserzione sui valori nulli dopo i passaggi critici

Alcune colonne non devono mai contenere valori nulli in nessun punto della pipeline. Verifichi con un'asserzione df['key_col'].notna().all() dopo ogni passaggio che potrebbe introdurre accidentalmente valori nulli, ad esempio un merge che non riesce a trovare una corrispondenza per alcune righe (producendo NaN nelle colonne unite) oppure una chiamata a map() che restituisce NaN per i valori senza corrispondenza. Faccia in modo che queste asserzioni siano le ultime due righe di ogni funzione di trasformazione che modifica tali colonne.

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

Creazione di una suite di test per i passaggi della pipeline

Scriva unit test per ogni funzione della pipeline usando piccoli DataFrame creati manualmente, che isolino la logica da verificare. Ogni test dovrebbe: predisporre un input minimo, chiamare la funzione e verificare le proprietà dell'output. Per pipeline semplici è sufficiente usare l'assert integrato in Python; nei progetti più grandi, usi pytest per eseguire automaticamente tutti i test prima del deployment.

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()

Test dei casi limite

Test efficaci coprono non solo il percorso previsto, ma anche i casi limite: input completamente nullo, DataFrame vuoto, DataFrame con una sola riga e colonne con valori estremi. Un DataFrame vuoto dovrebbe restituire un DataFrame vuoto, non generare un errore. Un DataFrame con una sola riga dovrebbe produrre il risultato corretto. Testi esplicitamente questi casi per assicurarti che in produzione la pipeline li gestisca correttamente quando arrivano dati insoliti.

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()

Test di integrazione: pipeline completa su dati di esempio

Oltre agli unit test sulle singole funzioni, scriva un test di integrazione che esegua la pipeline completa su un piccolo campione rappresentativo di dati reali. Verifichi con asserzioni che l'output abbia il numero previsto di colonne, che la colonna chiave sia univoca e che i ricavi totali rientrino in un intervallo plausibile. Questo controllo end-to-end individua i bug nell'interazione tra i passaggi che gli unit test non riescono a rivelare.

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)

Test continui con pytest

Man mano che la pipeline cresce, raccolga tutti i test in una directory tests/ ed esegua pytest dalla riga di comando. Un file conftest.py può creare fixture condivise, come DataFrame di esempio. Integri pytest nella pipeline CI/CD, in modo che ogni modifica al codice esegua automaticamente tutti i test prima del deployment. Un test non superato blocca il deployment, impedendo al codice non funzionante di arrivare in produzione.

# 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')

Le asserzioni come documentazione vivente

Ogni asserzione nella pipeline è sia una protezione sia un elemento di documentazione: dichiara in una forma leggibile dalla macchina come devono apparire i dati in quel punto. Quando un nuovo analista entra nel progetto e legge il codice della pipeline, le asserzioni gli illustrano i contratti sui dati senza richiedere un documento di specifiche separato. Tratti ogni asserzione come farebbe con un commento: renda il messaggio abbastanza descrittivo da chiarire la regola di business che impone.

# 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.')

Verifica rapida

Verifichi la comprensione dei concetti di analisi dei dati presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato a: inserire nella pipeline asserzioni per il controllo del numero di righe, dell'esistenza delle colonne, dell'intervallo dei valori e dei valori nulli, scrivere unit test per le singole funzioni di trasformazione includendo i casi limite e eseguire test di integrazione e trattare le asserzioni come documentazione vivente dei contratti sui dati. Ora vedremo come programmare e registrare le esecuzioni della pipeline per automatizzarle quotidianamente.

Domande Frequenti

La lezione «Testare i passaggi della pipeline con asserzioni» è gratuita?

Sì — il testo completo di «Testare i passaggi della pipeline con asserzioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Pandas & NumPy Academy, passa a CoddyKit PRO. Il corso Pandas & NumPy Academy include 4 lezioni in totale.

Cosa imparerò in «Testare i passaggi della pipeline con asserzioni»?

Aggiunga a ogni fase controlli sul numero di righe, asserzioni sui valori nulli e verifiche sulle colonne attese, in modo che gli errori emergano immediatamente. Eserciti Pandas & NumPy Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Pandas & NumPy Academy?

Non è richiesta alcuna esperienza precedente. Pandas & NumPy Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Testare i passaggi della pipeline con asserzioni»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Pandas & NumPy Academy?

Sì. Ogni lezione Pandas & NumPy Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Strutturare i passaggi di trasformazione come funzioni
  2. Parametrizzare le pipeline con dict di configurazione
  3. Testare i passaggi della pipeline con asserzioni
  4. Pianificare e registrare le esecuzioni della pipeline
← Torna a Pandas & NumPy Academy