0Pricing
Pandas & NumPy Academy · 课时

使用断言测试 pipeline 步骤

在每个阶段添加行数检查、空值断言和预期列保护,让错误立即显现。

使用断言测试 pipeline 步骤 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

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.

常见问题解答

「使用断言测试 pipeline 步骤」课时是免费的吗?

是的 — 「使用断言测试 pipeline 步骤」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。

「使用断言测试 pipeline 步骤」这节课中我会学到什么?

在每个阶段添加行数检查、空值断言和预期列保护,让错误立即显现。 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Pandas & NumPy Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「使用断言测试 pipeline 步骤」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?

能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 将转换步骤组织为函数
  2. 使用配置字典参数化 pipeline
  3. 使用断言测试 pipeline 步骤
  4. 调度并记录 pipeline 运行
← 返回 Pandas & NumPy Academy