본문 바로가기

시스템/Devops

uv sync 후 pytest가 사라졌을 때 복구 방법

의존성 선언을 수정한 직후 uv run pytest가 error: Failed to spawn: pytest로 끝난다면 테스트 코드보다 동기화 옵션을 먼저 확인해야 한다. 이 오류는 테스트 수집이나 실행 중에 발생한 실패가 아니라, 동기화된 가상환경에서 pytest 프로세스 자체를 만들지 못했다는 뜻이다. 실제 사례에서는 기본 uv sync가 개발용 extra를 제외하면서 테스트 러너를 프루닝했고, Caused by: No such file or directory (os error 2)가 이어졌다.

실패가 발생한 정확한 순서

작업의 시작은 사용되지 않는 테스트용 의존성 fakeredis를 선언에서 제거하는 것이었다. 실제 import는 0건이었고 이름은 주석에만 남아 있었으므로 미사용 의존성이라는 판단에는 문제가 없었다. 문제는 선언을 바꾼 다음 평소처럼 기본 uv sync를 실행해도 기존 테스트 환경이 유지될 것이라고 본 데 있었다.

기본 동기화가 끝난 뒤 테스트를 실행하자 pytest의 테스트 결과가 출력된 것이 아니라 프로세스 생성 오류가 발생했다. 재현 순서는 다음과 같으며, 내부 저장소의 이름과 디렉터리는 일반 자리표시자로 바꿨다. 이 단계에서 테스트 옵션이나 개별 테스트 파일을 바꿔도 실행 파일이 없으므로 결과는 달라지지 않는다.

문제가 발생한 재현 순서 — 프로젝트 디렉터리의 터미널에서 실행

cd <project-directory>
# import가 0건인 의존성을 선언에서 제거한 뒤 기본 동기화
uv sync

uv run pytest <test-directory> -q -p no:warnings
# error: Failed to spawn: pytest
# Caused by: No such file or directory (os error 2)

fakeredis 제거와 pytest 소실은 서로 독립적인 우연이 아니었다. 의존성 파일 변경 직후 실행한 동기화가 환경을 선언 상태에 맞추는 과정에서 dev extra를 제외했고, 그 결과 개발 도구인 pytest가 환경에서 제거됐다. 따라서 원인은 제거한 라이브러리의 런타임 동작이 아니라 어떤 의존성 집합으로 환경을 다시 구성했는지에 있다.

테스트 실패와 실행기 소실을 구분한다

pytest가 실행된 뒤라면 테스트 수집 오류, import 오류, assertion 실패처럼 테스트 계층의 메시지가 나타난다. 반면 Failed to spawn: pytest와 No such file or directory (os error 2)는 테스트 코드에 진입하기 전 경계에서 발생한다. 실패 지점은 테스트 내부가 아니라 동기화된 가상환경에서 테스트 러너를 시작하는 구간이다.

uv 동기화 옵션에 따른 pytest 실행 흐름

[의존성 선언 수정]
         |
         v
   +-----+------------------+
   |                        |
   v                        v
[기본 uv sync]       [uv sync --extra dev]
   |                        |
   v                        v
[dev extra 제외]     [앱 의존성 + 개발 도구]
   |                        |
   v                        v
[pytest 프루닝]       [부분 테스트]
   |                        |
   v                        v
[uv run pytest]       [mypy 타입 검사]
   |                        |
   v                        v
[프로세스 생성 실패]  [전체 테스트]
                            |
                            v
                  [419 passed / 검증 완료]

처음에는 오류 문구만 보고 pytest 실행 파일이나 PATH 문제일 가능성을 생각할 수 있었다. 그러나 직전에 실행한 작업이 의존성 선언 수정과 기본 uv sync였고, 저장소의 테스트 러너는 개발용 extra에 포함돼 있었다. 기록에는 PATH를 수정하거나 pytest를 전역으로 설치한 과정이 없으며, extra를 포함한 재동기화만으로 실행 환경이 복구됐다.

관찰한 증상 판단할 원인 조치
Failed to spawn: pytest pytest 프로세스를 생성할 수 없음 동기화에 포함된 extra 확인
No such file or directory (os error 2) 현재 가상환경에 실행기가 없음 개발 extra를 포함해 재동기화
의존성 수정 직후 발생 환경이 새 선언 상태로 프루닝됨 직전에 실행한 uv sync 옵션 확인
pytest가 다시 시작됨 실행기 복구만 확인된 상태 타입 검사와 전체 테스트까지 실행

기본 sync와 개발 extra의 차이

이 저장소에서 재현 가능한 개발 환경 동기화 명령은 기본 uv sync가 아니라 uv sync --extra dev였다. 기본 명령은 현재 선택된 의존성 집합에 환경을 맞추기 때문에, 선택되지 않은 dev extra의 패키지가 기존 환경에 계속 남는다고 가정할 수 없다. 이미 설치돼 있던 pytest도 선언 선택에서 제외되면 프루닝 대상이 될 수 있다.

문제가 된 명령과 수정한 명령의 차이는 pytest를 개별 설치하는 데 있지 않다. 저장소가 선언한 개발 의존성 집합 전체를 선택하도록 동기화 옵션을 바꾸는 것이 핵심이다. 다음 대비처럼 테스트 실행 전에 환경 구성 명령부터 수정해야 한다.

문제가 된 동기화와 수정한 동기화의 대비 — 프로젝트 디렉터리의 터미널에서 실행

# 문제: 개발 extra가 선택되지 않음
uv sync

# 수정: 테스트 러너가 선언된 개발 extra를 포함
uv sync --extra dev

pytest만 임시로 별도 설치하면 당장의 프로세스 생성 오류는 가릴 수 있지만, 저장소가 기대하는 다른 개발 도구와 버전 구성을 함께 복구했다는 근거가 되지 않는다. 전역 pytest를 사용하는 방식도 프로젝트 가상환경의 선언 상태와 실행 상태를 분리한다. 이 사례에서는 별도 설치 대신 --extra dev를 지정해 애플리케이션 의존성과 개발 도구를 함께 동기화했다.

개발 환경을 복구하는 실행 순서

복구는 프로젝트 디렉터리에서 개발 extra를 명시해 다시 동기화하는 것으로 시작한다. 동기화가 성공한 다음 동일한 uv 환경을 통해 pytest를 실행해야 전역 실행 파일이 우연히 선택되는 상황을 피할 수 있다. 작업 노트에서 확인된 복구 명령은 uv sync --extra dev였다.

개발 환경 복구와 테스트 진입 확인 — 프로젝트 디렉터리의 터미널에서 실행

cd <project-directory>
uv sync --extra dev
uv run pytest <test-directory> -q -p no:warnings

이 순서가 성공하면 최소한 pytest 실행기가 현재 환경에 다시 포함됐고 테스트 진입이 가능하다는 사실을 확인할 수 있다. 다만 부분 테스트나 -q 실행 성공만으로 의존성 제거의 영향이 없다고 단정할 수는 없다. 제거한 의존성은 import가 0건이었지만, 최종 검증은 정적 검사와 전체 테스트를 기준으로 수행됐다.

복구를 명령 실행 성공으로 끝내지 않는다

환경을 복구한 뒤에는 타입 검사와 전체 테스트를 연속으로 실행한다. 기록에 사용된 도구는 uv run mypy src와 uv run pytest tests -p no:warnings였으며, 발행용 명령에서는 내부 경로를 일반적인 소스·테스트 디렉터리 자리표시자로 바꾼다. 두 명령 모두 복구한 동일 환경에서 실행해야 한다.

복구 후 타입 검사와 전체 테스트 — 프로젝트 디렉터리의 터미널에서 실행

uv run mypy <source-directory>
uv run pytest <all-tests-directory> -p no:warnings
# 확인된 실제 결과: 419 passed in 68.80s (0:01:08)

전체 테스트의 확인된 결과는 419 passed in 68.80s (0:01:08)였다. 이 수치는 pytest 명령이 다시 시작됐다는 사실뿐 아니라 전체 419개 테스트가 통과했다는 검증 결과다. 의존성 제거가 타입 분석이나 전체 테스트에 영향을 주지 않았다는 판단도 이 단계 이후에만 가능하다.

같은 오류에서 적용할 진단 순서

첫째, 오류가 테스트 실행 후의 실패인지 프로세스 생성 전의 실패인지 구분한다. Failed to spawn과 운영체제의 파일 없음 오류가 함께 나오면 테스트 assertion이나 fixture보다 실행기 존재 여부를 먼저 본다. 둘째, 오류 직전에 의존성 선언을 바꾸거나 uv sync를 실행했는지 확인한다.

셋째, 테스트 러너가 기본 의존성인지 개발 extra인지 저장소 선언을 기준으로 확인한다. 넷째, 해당 저장소가 요구하는 extra를 명시해 환경 전체를 다시 동기화하고, 같은 uv 환경에서 테스트를 실행한다. 이 사례의 수정 명령은 uv sync --extra dev였으며 PATH 변경이나 전역 pytest 설치는 필요하지 않았다.

동일 증상에 적용하는 전체 복구 절차 — 프로젝트 디렉터리의 터미널에서 실행

uv sync --extra dev
uv run pytest <test-directory> -q -p no:warnings
uv run mypy <source-directory>
uv run pytest <all-tests-directory> -p no:warnings

마지막으로 부분 실행, 타입 검사, 전체 테스트 순서로 검증 범위를 넓힌다. pytest가 다시 실행되는 것과 의존성 정리가 안전하게 끝난 것은 서로 다른 확인 항목이다. 기본 uv sync 이후 갑자기 pytest가 사라진 경우에는 테스트 명령을 고치기 전에 선택된 extra와 프루닝 결과를 확인하는 것이 가장 짧은 진단 경로다.