무색
기술블로그
에세이
연구
프로덕트
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

[email protected]

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관
INCHEON, KR
Post #64 Key Visual — sumi-e style
크로스커팅
2026. 1. 30.

python-guide 스킬의 진화: 실전에서 발견한 테스트 누락 패턴

pythonpytestclaude-codetestinggpu-pipeline

Claude Code용 python-guide 스킬을 처음 만들었을 때는 꽤 자신 있었다. uv로 패키지 관리하고 ruff로 린트 걸고 pytest로 테스트 짜는 패턴을 정리해두면 대부분의 Python 프로젝트에서 잘 먹힐 거라고 생각했다.

실제로 그랬다. 처음 몇 개 프로젝트에서는. 그런데 GPU 모델 3개를 subprocess로 돌리는 transcription 파이프라인을 만들기 시작하면서 스킬의 빈 곳이 보이기 시작했다.

python-guide 스킬, 처음에 뭘 담았나

python-guide는 Claude Code가 Python 프로젝트를 작업할 때 자동으로 적용하는 가이드라인이다. 핵심 스택은 이렇다.

  • uv로 의존성 관리 (uv add)
  • ruff + ty로 린트/포매팅/타입체크
  • pytest로 테스트, fixture 패턴과 AAA 구조
  • SQLAlchemy 2.0 + Pydantic dataclass로 ORM과 DTO
  • Hydra로 설정 관리

테스트 관련해서는 pytest 사용, fixture 패턴, AAA(Arrange-Act-Assert) 구조 정도만 적어뒀다. 웹 앱이나 데이터 처리 파이프라인에서는 이 정도면 충분했다.

문제는 GPU가 끼어들면서부터였다.

실전에서 드러난 빈 곳

transcription 파이프라인은 화자 분리(pyannote) → 음성 인식(VibevoiceXL) → 강제 정렬(Qwen) 순으로 GPU 모델 3개를 돌린다. 각 모델이 서로 다른 torch 버전을 요구해서 독립 venv + subprocess로 격리했는데, 이 구조를 테스트하려니 기존 스킬에 답이 없었다.

구체적으로 세 가지가 빠져 있었다.

1. GPU 의존 테스트 격리

초기에는 모든 테스트가 GPU를 필요로 했다. CI에서 돌릴 수가 없으니 개발 중 피드백 루프가 느려졌다. GPU가 없어도 검증할 수 있는 로직과 GPU가 반드시 필요한 부분을 나눠야 했는데, 스킬에는 이런 분리 기준이 없었다.

결국 WORKER_URL 환경변수 유무로 통합 테스트를 자동 skip하는 패턴을 만들었다.

WORKER_URL = os.environ.get("WORKER_URL")

@pytest.mark.skipif(not WORKER_URL, reason="WORKER_URL not set")
def test_diarize_via_http():
    resp = httpx.post(f"{WORKER_URL}/run/diarize", json={"wav_path": "..."})
    # NDJSON 스트리밍 응답 파싱 ...

2. subprocess worker 모킹

파이프라인의 각 단계가 독립 프로세스라서 일반적인 함수 mock으로는 안 됐다. subprocess.run을 mock해서 각 worker의 JSON 응답을 시뮬레이션하는 패턴이 필요했다.

def test_correct_parses_structured_output(mock_subprocess, segments):
    mock_subprocess.return_value = CompletedProcess(
        args=[], returncode=0,
        stdout=json.dumps({
            "structured_output": {
                "segments": [{"index": 0, "text": "교정된 텍스트"}]
            },
            "usage": {"input_tokens": 100, "output_tokens": 50},
            "total_cost_usd": 0.001,
        }),
    )
    result = correct(segments)
    assert result[0].text == "교정된 텍스트"

PersistentWorker(상시 떠 있는 worker 서버) 테스트는 더 까다로웠다. stdin/stdout/stderr을 모두 mock하고 SimpleQueue와 threading.Event의 상호작용까지 시뮬레이션해야 했다.

3. Fixture 기반 데이터 흐름 테스트

GPU 없이도 파이프라인의 데이터 변환 로직을 검증하고 싶었다. 실제 파이프라인 출력을 JSON으로 캡처해서 tests/fixtures/에 넣어두고, 각 단계의 입출력이 데이터 모델 스펙에 맞는지 확인하는 방식을 썼다.

def test_diarize_to_asr_data_flow(diarize_data, asr_data):
    for seg in diarize_data:
        assert hasattr(seg, "speaker")
        assert hasattr(seg, "start")
        assert hasattr(seg, "end")
    for seg in asr_data:
        assert hasattr(seg, "text")
        assert hasattr(seg, "language")

Golden File Testing이라고도 불리는 이 패턴은, 파이프라인 출력 형식이 바뀌면 fixture도 같이 갱신해야 한다는 함정이 있다. 한번 fixture 갱신을 까먹었더니 테스트가 거짓 통과해서 한참 뒤에야 버그를 발견한 적이 있다.

3단계 테스트 피라미드

이런 시행착오 끝에 정착한 구조는 3단계 테스트 피라미드다.

맨 아래는 GPU가 전혀 필요 없는 단위 테스트다. SRT 생성 로직, 오디오 추출 mock, Claude Code CLI subprocess mock, Worker 서버 mock 등이 여기에 들어간다. CI에서 매 커밋마다 돌릴 수 있다.

가운데는 fixture 기반 데이터 흐름 테스트다. 실제 파이프라인 출력에서 추출한 JSON(diarize, asr, corrected, aligned)을 써서 각 단계의 데이터 모델 변환을 검증한다. GPU 없이 돌아가지만 실제 데이터 형태를 그대로 쓴다는 점에서 단위 테스트보다 현실적이다.

꼭대기는 HTTP 통합 테스트다. 실제 worker 서버에 요청을 보내서 전체 파이프라인을 검증한다. WORKER_URL 환경변수가 없으면 자동으로 건너뛴다.

스킬이 진화하는 방식

이 경험에서 얻은 가장 큰 교훈은 스킬 자체의 설계 방식에 관한 거다.

처음에 python-guide를 만들 때 나는 "완벽한 가이드"를 목표로 했다. 모든 상황을 미리 커버하려고 했다. 근데 그건 불가능하다. 어떤 가이드라인도 실전에서 만나는 모든 상황을 예측할 수 없다.

더 나은 접근은 이거다.

  1. 스킬을 적용해서 프로젝트를 진행한다
  2. 스킬이 커버하지 못하는 상황을 만난다
  3. 해결책을 찾고 스킬에 반영한다
  4. 다음 프로젝트에서 개선된 스킬을 쓴다

이 루프가 빠르게 돌수록 스킬이 빨리 성숙해진다. transcription 프로젝트에서 GPU 테스트 격리 패턴을 발견하고 스킬에 추가한 뒤, 이미지 생성 파이프라인에서도 같은 패턴을 바로 적용할 수 있었다.

정리하면

python-guide 스킬의 초기 버전에는 GPU 의존 테스트 격리, subprocess mock 패턴, fixture 기반 데이터 흐름 테스트가 빠져 있었다. 웹 앱 중심의 경험만으로는 ML 파이프라인의 테스트 요구사항을 예측하기 어려웠기 때문이다.

스킬은 한번 만들어두고 끝나는 게 아니다. 실전 프로젝트에서 부딪히고 깨지면서 점점 두꺼워진다. 완벽한 가이드를 처음부터 만들려 하기보다 빠르게 적용하고 빠르게 피드백받는 루프를 돌리는 편이 훨씬 낫다.

자주 묻는 질문

GPU 의존 테스트를 CI에서 어떻게 격리하나요?
WORKER_URL 같은 환경변수 유무로 통합 테스트를 자동 skip하는 pytest.mark.skipif 패턴을 사용합니다. GPU 없이도 검증 가능한 로직과 GPU 필수 부분을 분리하는 것이 핵심입니다.
subprocess로 호출하는 ML 모델을 테스트하려면 어떻게 mock하나요?
subprocess.run을 mock해서 CompletedProcess의 stdout에 JSON 응답을 시뮬레이션합니다. PersistentWorker는 stdin/stdout/stderr과 SimpleQueue, threading.Event까지 mock해야 합니다.
Claude Code 스킬을 효과적으로 개선하는 방법은?
처음부터 완벽한 가이드를 만들려 하지 말고, 실전 프로젝트에서 빈 곳을 발견할 때마다 반영하는 피드백 루프를 빠르게 돌리는 것이 가장 효과적입니다.
크로스커팅(18/18)
Prev

2주간 20개 프로젝트에서 만난 삽질 패턴 총정리