Development4 min read

간헐적 테스트 실패를 AI와 분석하기 전: 반복 실행 기록과 공유 상태 수집 순서

2026년 10월 19일4 min read작성자 Flature

같은 조건의 반복 실행 기록을 먼저 확보하고, 단독 실행·순서 변경·병렬 실행을 비교해 공유 상태의 영향을 좁히는 방법을 설명합니다. AI에게는 관찰 사실과 원인 가설을 분리해 전달하고, 다음 검증 실험을 설계하도록 요청합니다.

간헐적 테스트 실패를 AI와 분석하기 전: 반복 실행 기록과 공유 상태 수집 순서

간헐적으로 실패하는 테스트를 AI와 분석할 때는 실패 로그 하나보다 실행 조건이 붙은 반복 실행 기록을 먼저 수집해야 합니다. 이후 단독 실행, 다른 테스트 뒤의 실행, 병렬 실행을 비교하면서 프로세스 내부 상태와 외부 공유 자원을 나누어 확인합니다. AI에게 원인을 바로 추측하게 하기보다, 어떤 조건에서 실패가 달라지는지 판단할 근거를 제공하는 순서입니다.

먼저 성공과 실패를 같은 형식으로 기록하기

실패한 실행만 모으면 성공한 실행과 무엇이 달랐는지 알기 어렵습니다. 같은 커밋과 실행 명령을 기준으로 반복하고, 성공·실패 여부와 조건을 함께 남깁니다. 자동 재시도가 있다면 최초 실행과 재시도 결과를 분리합니다. 최종 성공만 기록하면 간헐적 실패가 가려집니다.

  • 고정 조건: 커밋, 런타임과 테스트 도구 버전, 의존성 잠금 파일, 운영체제 또는 컨테이너 이미지 식별자
  • 실행 조건: 정확한 명령, 대상 테스트, 실행 순서, 워커 수, 무작위 시드와 재시도 설정
  • 실행별 결과: 실행 ID, 시작 시각, 소요 시간, 종료 코드, 실패한 검증문과 스택 추적
  • 관련 자원: 프로세스·워커 ID, 테스트용 DB와 스키마, 캐시 키 접두사, 임시 디렉터리, 포트

모든 환경변수를 통째로 수집할 필요는 없습니다. 동작에 영향을 주는 설정만 선별하고 비밀값은 제외합니다. 서로 다른 실행이 같은 자원을 사용했는지는 알 수 있도록 자원 식별자를 일관된 별칭으로 치환하면 분석에 도움이 됩니다.

새 프로세스에서 반복해 기준선 만들기

먼저 문제가 된 테스트 하나를 같은 조건으로 여러 번 실행합니다. 다음은 Python과 pytest가 설치되어 있고, 저장소 루트에서 해당 테스트를 실행할 수 있다는 전제의 Bash 예시입니다. 외부 서비스가 필요하다면 운영 환경과 분리된 테스트용 자원을 사용합니다.

log_dir=$(mktemp -d)
for run in {1..20}; do
  if python -m pytest tests/test_profile.py::test_default_language -q \
    > "$log_dir/run-$run.log" 2>&1; then
    status=0
  else
    status=$?
  fi
  printf '%s,%s\n' "$run" "$status" >> "$log_dir/results.csv"
done
printf 'logs: %s\n' "$log_dir"

이 명령은 실행 번호별 로그와 종료 코드를 저장합니다. 0은 성공을 뜻하지만, 0이 아닌 종료 코드가 모두 검증 실패를 뜻하지는 않습니다. 수집 오류나 실행 중단도 로그에서 구분해야 합니다. 20회는 시작점일 뿐이며, 전부 성공해도 간헐적 실패가 없다고 증명하지는 못합니다.

매번 새 Python 프로세스를 실행한다는 점이 핵심입니다. 프로세스 내부 전역 변수는 보통 새로 시작하지만, DB 레코드·Redis 키·파일은 남을 수 있습니다. 이 결과만으로 공유 상태의 영향을 배제해서는 안 됩니다. 같은 프로세스 안에서 반복하는 실험은 별도 조건으로 기록합니다.

실행 순서와 공유 자원을 한 단계씩 분리하기

기준선을 얻었다면 한 번에 한 조건만 바꿉니다. 실행 방식과 DB 초기화를 동시에 바꾸면 어느 변화가 결과에 영향을 주었는지 판단하기 어렵습니다.

비교 실험달라지는 조건실패 양상이 달라질 때 확인할 대상
단독 실행과 특정 테스트 뒤 실행같은 프로세스에서 앞서 실행된 테스트전역 변수, 캐시, 환경변수 변경, 복구되지 않은 패치
A 다음 B와 B 다음 A테스트 순서앞 테스트가 남긴 상태와 뒤 테스트의 초기 상태 가정
직렬 실행과 병렬 실행동시 접근 여부같은 DB 행, 파일명, 캐시 키, 포트의 충돌
외부 자원 재사용과 실행별 자원 분리DB·캐시·디렉터리의 공유 범위정리 누락, 고정 식별자, 이전 실행의 잔여 데이터

순서 실험에서는 A와 B가 실제로 같은 프로세스와 관련 픽스처 범위 안에서 실행되는지 확인합니다. 테스트 도구나 플러그인이 순서를 바꿀 수 있으므로 요청한 순서와 실제 실행 순서를 함께 남깁니다.

병렬 실행에서만 실패한다고 곧바로 데이터 경합으로 확정하지는 않습니다. 병렬 실행에 따른 자원 부족이나 응답 지연도 가능합니다. 워커별 자원을 분리한 뒤에도 실패하는지 비교하면 가설을 더 좁힐 수 있습니다.

예시: 언어 설정 테스트의 순서 의존성을 AI와 좁히기

다음은 진단 절차를 설명하기 위한 가상 사례입니다. 기본 언어를 확인하는 B 테스트가 전체 실행에서 가끔 실패하고, 언어를 변경하는 A 테스트가 있다고 가정합니다.

# app/settings.py
OPTIONS = {"language": "ko"}

# tests/test_settings.py
from app.settings import OPTIONS

def test_change_language():  # A
    OPTIONS["language"] = "en"
    assert OPTIONS["language"] == "en"

def test_default_language():  # B
    assert OPTIONS["language"] == "ko"

이 코드에서 A는 공유 딕셔너리를 바꾸고 복구하지 않습니다. 같은 프로세스에서 A 다음 B를 실행하면 B는 변경된 값을 읽게 됩니다. B를 새 프로세스에서 단독 실행하면 이 상태 오염이 드러나지 않을 수 있습니다.

실제 분석에서는 코드를 보고 추측하는 데서 멈추지 않고, B 단독·A 다음 B·B 다음 A의 실행 기록과 B 시작 시점의 언어 값을 모읍니다. AI에게는 다음처럼 요청할 수 있습니다.

목표: B 테스트의 간헐적 실패 조건을 좁힌다.

첨부 자료:
- 조건별 반복 실행 결과와 실행별 로그
- 실제 테스트 순서와 프로세스 ID
- A/B 코드 및 관련 초기화·정리 픽스처
- B 시작 시점의 language 값

관찰 사실과 원인 가설을 구분해라.
각 가설에 대해 지지 근거, 아직 없는 증거,
가설을 구분할 다음 실험을 제시해라.
단독 실행과 같은 프로세스 내 연속 실행을 구별해라.
자료에 없는 실행 결과는 추정하지 마라.

이 예시의 수정 후보는 테스트마다 설정 객체를 새로 만들거나, 픽스처가 기존 값을 저장했다가 테스트 종료 시 복원하도록 하는 것입니다. 복원 방식은 기존 구조에 적용하기 쉽지만 공유 객체에 계속 의존합니다. 테스트별 객체 생성은 격리를 명확히 하는 대신 코드 구조를 바꿔야 할 수 있습니다. 어느 방식을 택하든 원래 실패를 유발한 순서로 다시 검증해야 합니다.

공유 상태로 설명되지 않을 때 확인할 것

단독 실행에서도 실패한다면 시간·난수·비동기 작업·외부 응답을 추가로 기록합니다. 타임존과 기준 시각, 난수 시드, 요청 식별자, 작업 시작·완료 이벤트가 후보입니다. 시드를 고정해도 스레드 실행 순서나 외부 서비스 응답까지 고정되는 것은 아닙니다.

단순히 대기 시간을 늘리거나 재시도를 추가하면 실패가 줄어도 원인은 남을 수 있습니다. 비동기 작업은 가능한 경우 완료 신호나 명시적 조건을 기다리도록 검토하고, 타임아웃 확대는 실제 지연 근거가 있을 때 판단합니다. 상세 로깅 자체가 타이밍을 바꿀 수 있으므로 진단 로그도 필요한 범위로 제한합니다.

실무에서는 먼저 실행별 기록을 만들고, 실패를 유발하는 최소 실행 순서를 좁힌 뒤, 공유 자원의 범위를 하나씩 분리하세요. AI의 제안은 그 기록으로 검증할 가설로 취급하고, 수정 후에는 같은 실패 조건과 관련 테스트 묶음에서 결과를 확인하면 됩니다.