단언으로 Pipeline 단계 test하기
각 단계에 행 개수 검사, null 단언, 예상 열 보호 조건을 추가해 errors가 즉시 드러나게 합니다.
단언으로 Pipeline 단계 test하기은(는) CoddyKit의 무료 Pandas & NumPy Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Pandas & NumPy Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Pandas & NumPy Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
파이프라인 단계를 테스트해야 하는 이유
오류 없이 실행되는 데이터 파이프라인도 조용히 잘못된 출력을 생성할 수 있습니다. 잘못된 필터로 행이 삭제되거나, 열에 잘못된 배수가 적용되거나, 조인 키가 고유하지 않아 병합 과정에서 행이 중복될 수 있습니다. 이러한 조용한 실패를 발견하는 유일한 방법은 파이프라인의 각 단계에서 데이터의 예상 속성을 확인하는 assertion을 포함하는 것입니다. Assertion을 사용하면 논리적 버그가 즉시 눈에 띄는 명확한 오류로 바뀝니다.
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.행 수 확인
각 필터링 단계가 끝난 후 결과 행 수가 예상 범위 안에 있는지 assertion으로 확인하십시오. 행이 너무 적으면 필터가 지나치게 엄격했다는 뜻이고, 너무 많으면 조인으로 레코드가 중복되었다는 뜻입니다. 예상 범위는 입력에 대한 비율로 표현하십시오. 예를 들어 정상적인 데이터에서 null 제거 단계는 행의 30%를 초과하여 삭제해서는 안 됩니다. 이 보호 장치는 상위 데이터 원본의 예기치 않은 데이터 품질 변화를 포착합니다.
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열 존재 여부 확인
각 변환 함수가 실행된 후 예상되는 모든 출력 열이 존재하는지 assertion으로 확인하십시오. 이름 변경 단계에서 실수로 잘못된 키를 사용하면 결과 열이 누락되며, 다른 단계에서 해당 열을 사용하려 할 때가 되어서야 오류가 발생합니다. 변환 직후에 assertion을 실행하면 세 단계 뒤에 혼란스러운 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값 범위 assertion
revenue 열을 계산한 후 값이 음수가 아닌지 assertion으로 확인하십시오. 날짜를 구문 분석한 후에는 예상 연도 범위에 속하는지 확인하십시오. 범주를 인코딩한 후에는 예상하지 못한 코드가 나타나지 않았는지 확인하십시오. 각 assertion은 예상되는 동작을 문서화하고 위반을 조기에 발견하는 데이터 계약입니다. 자동화된 파이프라인 실행에서 오류를 발생시키도록 print 문이 아니라 assertion으로 작성하십시오.
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.')병합 후 중복 방지 보호 장치
일반적인 데이터 버그 중 하나는 다대다 병합으로 인해 행이 의도치 않게 늘어나는 것입니다. 모든 pd.merge() 후에 행 수가 예상과 일치하는지 assertion으로 확인하십시오. 일반적으로 왼쪽 병합에서는 왼쪽 DataFrame의 행 수를 초과하지 않아야 합니다. 또한 고유해야 하는 경우 키 열이 고유한지도 확인하여 의도치 않은 교차 조인을 즉시 발견하십시오.
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
특정 열은 파이프라인의 어느 시점에서도 null이어서는 안 됩니다. 병합에 실패하여 일부 행이 일치하지 않거나(그 결과 병합된 열에 NaN이 생성됨), 매핑되지 않은 값에 대해 NaN을 반환하는 map() 호출처럼 실수로 null을 만들 수 있는 모든 단계 후에 df['key_col'].notna().all()을 assertion으로 확인하십시오. 해당 열을 처리하는 모든 변환 함수의 마지막 두 줄에 이러한 assertion을 배치하십시오.
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파이프라인 단계를 위한 테스트 모음 만들기
테스트하려는 로직만 분리할 수 있도록 직접 만든 작은 DataFrame을 사용하여 각 파이프라인 함수의 단위 테스트를 작성하십시오. 각 테스트는 최소한의 입력을 준비하고, 함수를 호출한 다음, 출력 속성을 assertion으로 확인해야 합니다. 간단한 파이프라인에는 Python에 내장된 assert만으로 충분하지만, 규모가 큰 프로젝트에서는 배포 전에 모든 테스트를 자동으로 실행하도록 pytest를 사용하십시오.
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()경계 사례 테스트
좋은 테스트는 정상적인 경우뿐 아니라 모든 값이 null인 입력, 빈 DataFrame, 한 행짜리 DataFrame, 극단적인 값이 있는 열과 같은 경계 사례도 다룹니다. 빈 DataFrame은 오류가 아니라 빈 DataFrame을 반환해야 합니다. 한 행만 있는 DataFrame도 올바른 결과를 계산해야 합니다. 운영 환경에서 예외적인 데이터가 들어왔을 때 파이프라인이 안정적으로 처리하는지 확인하려면 이러한 사례를 명시적으로 테스트하십시오.
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()통합 테스트: 샘플 데이터로 전체 파이프라인 실행
개별 함수에 대한 단위 테스트 외에도 실제 데이터를 대표하는 작은 샘플에서 전체 파이프라인을 실행하는 통합 테스트를 작성하십시오. 출력에 예상한 수의 열이 있는지, 키 열이 고유한지, 총 revenue가 타당한 범위에 있는지를 assertion으로 확인하십시오. 이 종단 간 확인은 단위 테스트로는 드러나지 않는 단계 간 상호작용의 버그를 발견합니다.
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)pytest를 사용한 지속적 테스트
파이프라인이 성장하면 모든 테스트를 tests/ 디렉터리에 모으고 명령줄에서 pytest로 실행하십시오. conftest.py 파일을 사용하면 샘플 DataFrame과 같은 공유 픽스처를 만들 수 있습니다. pytest를 CI/CD 파이프라인에 통합하여 코드가 변경될 때마다 배포 전에 모든 테스트가 자동으로 실행되도록 하십시오. 테스트가 실패하면 배포가 중단되어 문제가 있는 코드가 운영 환경에 도달하는 것을 막을 수 있습니다.
# 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')살아 있는 문서로서의 assertion
파이프라인의 각 assertion은 보호 장치인 동시에 문서의 일부입니다. 즉, 해당 시점에 데이터가 어떤 형태여야 하는지를 기계가 읽을 수 있는 형식으로 명시합니다. 새로운 분석가가 프로젝트에 합류하여 파이프라인 코드를 읽을 때 assertion을 통해 별도의 사양 문서 없이도 데이터 계약을 알 수 있습니다. 각 assertion을 주석처럼 다루고, 적용하는 업무 규칙을 이해할 수 있을 만큼 메시지를 구체적으로 작성하십시오.
# 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.')빠른 확인
이번 레슨에서 배운 데이터 분석 개념을 제대로 이해했는지 확인하십시오.
레슨 요약
이번 레슨에서는 행 수, 열 존재 여부, 값 범위, null 확인을 파이프라인 내부 assertion으로 포함하는 방법, 경계 사례를 포함하여 개별 변환 함수의 단위 테스트를 작성하는 방법, 통합 테스트를 실행하고 assertion을 살아 있는 데이터 계약 문서로 다루는 방법을 배웠습니다. 다음으로는 자동화된 일일 실행을 위해 파이프라인 실행을 예약하고 기록하는 방법을 살펴보겠습니다.
AI 튜터와 함께 Python을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 30
- 레슨
- 120
자주 묻는 질문
“단언으로 Pipeline 단계 test하기” 강의는 무료인가요?
네 — “단언으로 Pipeline 단계 test하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Pandas & NumPy Academy 강의 전체를 잠금 해제할 수 있습니다. Pandas & NumPy Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“단언으로 Pipeline 단계 test하기”에서 뭘 배우나요?
각 단계에 행 개수 검사, null 단언, 예상 열 보호 조건을 추가해 errors가 즉시 드러나게 합니다. 브라우저에서 직접 실행하는 실습 코드로 Pandas & NumPy Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Pandas & NumPy Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Pandas & NumPy Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“단언으로 Pipeline 단계 test하기” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Pandas & NumPy Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Pandas & NumPy Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 변환 단계를 함수로 구성하기
- 설정 딕셔너리로 Pipeline 매개변수화하기
- 단언으로 Pipeline 단계 test하기
- Pipeline 실행 예약과 로깅