동일한 커밋이 개발자 컴퓨터에서는 모든 테스트를 통과하지만 클라우드 Mac에서는 일부만 실행된다면, 가장 흔한 원인은 테스트 코드가 아니라 조용히 표류한 .xctestplan입니다. 누군가 Xcode에서 일시적으로 테스트를 건너뛰거나 진단 옵션을 끄고, 로컬 환경에서만 유효한 환경 변수를 계획 파일에 저장했을 수 있습니다. 이를 해결하려면 XCTestPlan을 그래픽 인터페이스의 부가 설정이 아닌 빌드 입력으로 취급해야 합니다.
테스트 계약의 경계부터 고정하기
재현 가능한 테스트 진입점에는 최소한 공유 Scheme, XCTestPlan, 실행 대상, 명령줄 인수가 포함되어야 합니다. 먼저 명령줄에서 Scheme과 계획을 실제로 찾을 수 있는지 확인합니다.
WORKSPACE="${WORKSPACE:-App.xcworkspace}"
SCHEME="${SCHEME:-App}"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-showTestPlans
출력에 예상한 계획이 없다면 먼저 Scheme이 Shared로 설정되어 있는지, 계획 파일이 저장소에 추가되어 있는지, Scheme의 Test 동작이 해당 계획을 참조하는지 확인합니다. 존재하지 않는 계획을 CI 인수로 임시 보완하지 마십시오. 그렇게 하면 로컬 진입점과 파이프라인이 계속 분리됩니다.
계획에서는 다음 항목을 명확히 검토해야 합니다.
| 항목 | 확인해야 할 사실 | 흔한 표류 |
|---|---|---|
| Test Targets | 어떤 단위 테스트와 UI 테스트를 실행할지 | 새 대상이 추가되지 않음 |
| Selected Tests | 지정된 테스트만 실행하는지 | 디버깅용 선택이 커밋됨 |
| Skipped Tests | 각 제외 항목에 담당자와 기한이 있는지 | 실패한 테스트가 영구적으로 숨겨짐 |
| Configurations | 언어, 지역, 시작 인수 | 로컬 설정이 기본 설정을 덮어씀 |
| Diagnostics | 충돌, 스레드, 성능 진단 정책 | 속도를 높이려다 실수로 비활성화됨 |
| Parallelization | 어떤 대상의 병렬 실행을 허용할지 | 공유 상태를 사용하는 테스트가 서로 간섭함 |
테스트를 건너뛰는 것은 수정이 아닙니다. 새로 추가되는 모든 skipped test는 코드 변경과 마찬가지로 검토를 거쳐야 하며, 복구 조건도 명시해야 합니다.
읽기 쉬운 정규화 차이 생성하기
XCTestPlan은 구조화된 파일이므로 원시 텍스트를 직접 검토하면 필드 순서와 자동 생성 식별자 때문에 핵심 변경 사항을 파악하기 어렵습니다. 실행 의미에 영향을 주지 않는 구성 식별자만 제거한 정규화 기준 파일을 저장소에 보관할 수 있습니다.
PLAN="App.xctestplan"
CURRENT=".ci/xctestplan.current.json"
BASELINE=".ci/xctestplan.baseline.json"
mkdir -p .ci
plutil -convert json -o - "$PLAN" |
jq -S 'del(.configurations[]?.id)' > "$CURRENT"
diff -u "$BASELINE" "$CURRENT"
처음 도입할 때는 $CURRENT를 직접 검토한 뒤 기준 파일로 복사하여 커밋합니다. 이후 파이프라인에서는 현재 파일만 생성하고 diff를 실행합니다. 대상, 제외 항목, 환경 변수, 인수, 진단 옵션은 모두 유지해야 합니다. “깔끔한 차이”를 얻기 위해 비즈니스 필드를 추가로 삭제해서는 안 됩니다.
정규화 스크립트도 테스트 인프라의 일부입니다. 스크립트 변경과 계획 변경은 동일한 병합 요청에서 함께 보여야 합니다. 그렇지 않으면 필터링 규칙을 한 번 완화하는 것만으로 이후의 모든 표류가 보이지 않게 될 수 있습니다.
민감한 값과 호스트 의존성 차단하기
계획 파일은 변수 이름을 저장하는 데는 적합하지만 토큰, 비밀번호, 개인 키 내용이나 개발자 디렉터리를 저장하는 데는 적합하지 않습니다. 먼저 활성화된 환경 변수를 재귀적으로 추출합니다.
plutil -convert json -o - App.xctestplan |
jq -r '
.. |
objects |
.environmentVariableEntries? // empty |
.[]? |
select(.enabled == true) |
[.key, .value] |
@tsv
'
출력을 검사할 때는 세 가지 유형을 중점적으로 차단합니다. 키처럼 보이는 긴 문자열, /Users/某人/ 형식의 절대 경로, 대화형 Shell에만 존재하는 도구 경로입니다. 실제 민감한 값은 CI의 통제된 환경에서 런타임에 주입해야 합니다. 테스트 코드는 변수 이름만 읽고, 값이 없으면 명확한 오류를 표시해야 합니다.
설정 재정의 우선순위가 통제되지 않도록 하기
XCTestPlan, Scheme, xcodebuild 인수, 테스트 코드는 모두 시작 인수를 설정할 수 있습니다. 하나의 우선순위를 정하는 것이 좋습니다. 계획에는 안정적인 기본값을 저장하고, CI는 민감한 값과 이번 실행의 식별자만 주입하며, 테스트 코드는 프로세스 수준 설정을 변경하지 않습니다. 파이프라인에서 -only-testing 또는 -skip-testing을 사용한다면 해당 인수를 검토 가능한 스크립트에 작성해야 하며, 작업 패널의 임시 입력란에 숨겨서는 안 됩니다.
고정된 대상으로 실행하고 증거 보존하기
먼저 xcrun simctl list devices available을 사용해 현재 노드에 설치된 기기를 선택하고 해당 UDID를 파이프라인 입력으로 전달합니다. “최신 시스템”처럼 모호하게 지정하는 것보다 기기와 런타임을 고정하는 편이 차이를 설명하기 쉽습니다.
DEVICE_UDID="${DEVICE_UDID:?Set DEVICE_UDID from simctl}"
RESULT_PATH="${RESULT_PATH:-artifacts/CI.xcresult}"
rm -rf "$RESULT_PATH"
mkdir -p "$(dirname "$RESULT_PATH")"
xcodebuild test \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-testPlan CI \
-destination "platform=iOS Simulator,id=$DEVICE_UDID" \
-resultBundlePath "$RESULT_PATH"
성공 여부와 관계없이 .xcresult, 전체 명령, 커밋 식별자, Xcode 버전, 선택한 기기 UDID를 모두 보존해야 합니다. 로그의 마지막 수십 줄만 잘라서 보관하지 마십시오. 테스트 미실행, 프로세스 충돌, 어설션 실패에는 서로 다른 증거가 필요하며, 결과 번들은 테스트 계층 구조, 첨부 파일, 진단 정보를 보존할 수 있습니다.
병렬 실행은 보수적인 값에서 시작해야 합니다. 동일한 데이터베이스, 고정 포트 또는 공유 파일에 의존하는 테스트 대상에는 병렬 실행을 바로 활성화하면 안 됩니다. 재시도로 경쟁 상태를 감추는 대신 상태 격리를 먼저 완성한 다음 대상을 하나씩 병렬 실행하도록 허용해야 합니다.
감사를 병합 게이트로 전환하기
최종 게이트는 정해진 순서로 실행해야 합니다. Scheme이 계획을 찾을 수 있는지 확인하고, 정규화 파일을 생성하고, 기준 파일과 비교하고, 민감한 값과 절대 경로를 검사하고, 제외 목록을 확인한 뒤 마지막으로 테스트를 실행합니다. 이렇게 하면 시뮬레이터를 시작하기 전에 구성 오류로 실패하므로 대기 및 진단 시간을 줄일 수 있습니다.
커밋하기 전에 다음 체크리스트로 다시 확인할 수 있습니다.
- Scheme이 공유되었고 계획 파일이 버전 관리에 포함되어 있습니다.
- 새 테스트 대상이 올바른 계획에 추가되었습니다.
- 모든 skipped tests에 사유, 담당자, 복구 조건이 있습니다.
- 계획에 실제 민감한 값이나 개인 디렉터리가 없습니다.
- CI에 테스트 범위를 재정의하는 숨겨진 인수가 없습니다.
- 기기 UDID, Xcode 버전, 결과 번들이 모두 기록됩니다.
- 정규화 규칙이 실행 의미를 지닌 필드를 삭제하지 않습니다.
- 실행이 실패해도
.xcresult가 보관됩니다.
XCTestPlan의 모든 변경 사항을 코드 검토에서 이해할 수 있게 되면, “로컬에서는 모두 통과했지만 클라우드에서는 일부 테스트가 누락되는” 현상은 더 이상 간헐적인 수수께끼가 아니라 실행 전에 차단할 수 있는 구성 차이가 됩니다.
자주 묻는 질문
공유 Xcode Scheme만으로는 왜 부족한가요?
Scheme은 실행 진입점을 정하지만 테스트 대상, 구성, 인자, 환경 변수, 언어와 제외 목록은 XCTestPlan에서 달라질 수 있습니다. CI에서는 세 계층을 함께 고정해야 합니다.
XCTestPlan에 접근 토큰을 저장해도 되나요?
저장하면 안 됩니다. 계획에는 변수 이름이나 민감하지 않은 기본값만 두고 실제 비밀값은 통제된 CI 환경에서 실행 시 주입하며 로그와 첨부 파일에도 남기지 않습니다.
정규화가 중요한 변경을 숨길 수 있나요?
필터가 과도하면 가능합니다. 실행 의미가 없는 자동 생성 ID만 제거하고 대상, 선택 및 제외 항목, 인자, 진단 옵션과 구성 이름은 유지해야 합니다.
개발 파이프라인에 고정 클라우드 Mac 구성하기
MangoVM은 M4와 M4 Pro 두 가지 Apple Silicon 물리 노드를 제공합니다. 대여 기간, 리전, 스토리지 추가 옵션을 선택하면 전체 주문 내역을 확인할 수 있습니다.