본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] CI/CD 파이프라인에 Model Unit Test와 Integration Test를 포함시키는 3가지 방법과 자동화 해결 전략

by Papa Martino V 2026. 4. 29.
728x90

Model Unit Test vs Integration Test
Model Unit Test vs Integration Test

1. MLOps의 핵심: 머신러닝 테스트 자동화의 필요성

전통적인 소프트웨어 개발에서 Unit Test가 코드의 논리적 결함을 잡아낸다면, 머신러닝(ML) 환경에서의 테스트는 데이터, 모델, 코드라는 세 가지 축을 모두 검증해야 합니다. 모델의 가중치가 업데이트되거나 학습 스크립트가 수정될 때마다 수동으로 성능을 점검하는 것은 비효율적일 뿐만 아니라 운영 환경에서의 리스크를 증대시킵니다. 본 가이드에서는 파이썬(Python) 환경에서 PytestGitHub Actions 또는 GitLab CI를 활용하여 모델의 유효성을 검증하는 유닛 테스트(Unit Test)와 전체 시스템 흐름을 확인하는 통합 테스트(Integration Test)를 CI/CD 파이프라인에 이식하는 구체적인 해결 방법을 제시합니다.


2. 유닛 테스트(Unit Test)와 통합 테스트(Integration Test)의 차이점 및 역할

ML 파이프라인에서 각 테스트 단계가 갖는 목적을 명확히 정의하는 것이 설계의 첫 단계입니다.

구분 Model Unit Test (유닛 테스트) Integration Test (통합 테스트)
검증 대상 개별 함수, 모델 레이어 출력 크기, 데이터 스키마 데이터 로드부터 추론 API 엔드포인트까지의 전체 흐름
실행 시점 코드 푸시(Push) 또는 PR(Pull Request) 생성 시 스테이징 환경 배포 전 또는 모델 빌드 완료 후
데이터 사용 모의 데이터(Mocking) 또는 아주 작은 샘플 데이터 검증용 데이터셋(Validation Set) 또는 실제 운영 DB 복제본
주요 도구 Pytest, Unittest, Great Expectations Docker Compose, Postman, FastAPI TestClient
해결 문제 차원 불일치, 하이퍼파라미터 타입 오류 인프라 호환성, 전처리-모델 간 데이터 정합성

3. 실무 적용을 위한 7가지 Python Test Example

개발자가 CI/CD 파이프라인(예: GitHub Actions)에 즉시 복사하여 적용할 수 있는 실무 코드 예제입니다.

예제 1: 모델 입력 및 출력 차원(Shape) 검증 유닛 테스트

모델 구조 변경 시 텐서 크기 불일치로 인한 런타임 에러를 방지합니다.

import pytest
import torch
from my_project.model import MyComplexModel

def test_model_output_shape():
    batch_size = 4
    input_dim = 128
    model = MyComplexModel(input_dim=input_dim)
    dummy_input = torch.randn(batch_size, input_dim)
    
    output = model(dummy_input)
    
    assert output.shape == (batch_size, 1), f"Expected shape (4, 1), got {output.shape}"
        

예제 2: 데이터 전처리 함수의 스키마 및 범위 검증

피처 엔지니어링 단계에서 예상치 못한 Null 값이나 이상치가 들어오는지 확인합니다.

import pandas as pd
from my_project.preprocess import scale_features

def test_preprocess_scaling_range():
    data = pd.DataFrame({'age': [20, 30, 40, 50], 'income': [1000, 2000, 3000, 4000]})
    processed_df = scale_features(data)
    
    # MinMaxScaler 적용 시 0과 1 사이여야 함
    assert processed_df['age'].min() >= 0
    assert processed_df['age'].max() <= 1
    assert not processed_df.isnull().values.any()
        

예제 3: 추론 API 엔드포인트 통합 테스트 (FastAPI 기준)

API 서버가 실행되었을 때 모델이 정상적으로 예측 결과를 반환하는지 확인합니다.

from fastapi.testclient import TestClient
from my_project.main import app

client = TestClient(app)

def test_predict_endpoint_integration():
    payload = {"features": [1.2, 0.5, 3.3, 0.1]}
    response = client.post("/predict", json=payload)
    
    assert response.status_code == 200
    data = response.json()
    assert "prediction" in data
    assert isinstance(data["prediction"], (int, float))
        

예제 4: 모델 성능 하한선(Threshold) 검증 테스트

새로 학습된 모델이 기존 모델보다 성능이 떨어지지 않는지 CI 과정에서 체크합니다.

import joblib
from sklearn.metrics import accuracy_score
from my_project.utils import load_test_data

def test_model_accuracy_threshold():
    model = joblib.load("models/latest_model.pkl")
    X_test, y_test = load_test_data()
    
    predictions = model.predict(X_test)
    acc = accuracy_score(y_test, predictions)
    
    THRESHOLD = 0.85
    assert acc >= THRESHOLD, f"Model accuracy {acc} is below required {THRESHOLD}"
        

예제 5: 모델 파일 무결성 및 가중치 변화 감지

가중치가 0이거나 NaN으로 수렴하는 '죽은 모델'이 배포되는 것을 막습니다.

import numpy as np

def test_model_weights_not_nan():
    model = load_my_pytorch_model("path/to/model.pt")
    for name, param in model.named_parameters():
        assert not torch.isnan(param).any(), f"NaN found in weights of {name}"
        assert torch.count_nonzero(param) > 0, f"All-zero weights found in {name}"
        

예제 6: GitHub Actions Workflow 설정 (YAML)

작성한 테스트 코드를 CI 환경에서 자동으로 실행하는 파이프라인 구성 방법입니다.

name: ML CI Pipeline
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run Model Unit Tests
        run: pytest tests/unit/
      - name: Run Integration Tests
        run: pytest tests/integration/
        

예제 7: 대용량 모델 로딩 시간 및 메모리 점유율 테스트

운영 환경의 리소스 제한(Memory Limit) 내에서 모델이 정상 동작하는지 검증합니다.

import time
import psutil
import os

def test_resource_usage_within_limits():
    process = psutil.Process(os.getpid())
    start_time = time.time()
    
    model = load_large_model() # 모델 로드 함수
    
    load_time = time.time() - start_time
    mem_usage = process.memory_info().rss / (1024 * 1024) # MB 단위
    
    assert load_time < 10, f"Loading took too long: {load_time}s"
    assert mem_usage < 2048, f"Memory usage too high: {mem_usage}MB"
        

4. 고도화된 해결 전략: 파이프라인 최적화 방법 3가지

  1. Data Mocking 및 버전 관리: 통합 테스트 시 매번 전체 DB를 불러올 수 없으므로, DVC(Data Version Control)를 통해 테스트용 정적 데이터셋을 버전화하여 관리합니다.
  2. Parallel Testing: 모델 테스트는 CPU/GPU 연산이 많이 필요하므로 Pytest-xdist를 사용하여 테스트를 병렬로 실행, CI 시간을 단축합니다.
  3. Artifact Logging: 테스트 실패 시 단순히 에러 메시지만 남기는 것이 아니라, 실패한 예측 샘플이나 Confusion Matrix 이미지를 CI Artifact로 업로드하여 원인을 즉시 파악하게 합니다.

5. 결론 및 향후 전망

CI/CD 파이프라인에 모델 테스트를 포함시키는 것은 단순히 코드를 검증하는 수준을 넘어, 인공지능 서비스의 신뢰성(Reliability)을 확보하는 과정입니다. 위에서 제시한 유닛 테스트와 통합 테스트 기법을 단계적으로 도입한다면, 모델 배포 시 발생하는 예기치 못한 사고를 90% 이상 예방할 수 있습니다. 향후에는 테스트 단계에서 Drift 탐지 로직까지 결합하여, 성능 저하 시 자동으로 배포를 차단하는 그레이스풀 서빙(Graceful Serving) 아키텍처로 진화하는 것이 MLOps의 지향점입니다.


참고 문헌 및 출처

  • Google Cloud Architecture Center: MLOps: Continuous delivery and automation pipelines in machine learning (2024).
  • Martin Fowler: The Practical Test Pyramid in ML Systems.
  • Pytest Documentation: Best practices for integration testing.
  • GitHub Actions Documentation: Automating Python workflows.
728x90