DevOps / CI·CD 2026년 4월 23일

2026-04-23 임대 클라우드 Mac CI: iOS 테스트 플랜, 병렬 XCTest, xcresult 조사

MacXCode 엔지니어링 팀 2026년 4월 23일 약 18분

iOS QA 소유자CI 유지자홍콩 / 일본 / 한국 / 싱가포르 / 미국의 임대 Apple Silicon Mac에서 xcodebuild test를 돌릴 때 임시 -only-testing 플래그에 금방 한계에 닿습니다. 이 2026-04-23 런북은 테스트 플랜을 표준화하고, destination으로 병렬화하며, xcresult 번들을 오브젝트 스토어에 안정적인 이름으로 보내는 방법을 보여 헤드리스 시뮬레이터 테스트, DerivedData + xcresult 분리, 병렬 아카이브 팬아웃을 보완합니다.

원격 CI에 테스트 플랜이 속하는 이유

*.xctestplan은 버전 관리된 의도입니다. 기본 테스트 대상, 옵션, 환경 변수 행렬을 나열해 팀이 Git에서 리뷰할 수 있습니다. 이는 CI YAML에 박힌 거대한 셸 문자열보다 낫습니다. 특히 포인트 릴리스 뒤 “실제로 어떤 구성이 돌았나” PM이 묻을 때 쓰입니다. 플랜을 단일 스킴에 묶고 스킴은 고정하세요. 플랜이 Debug/Release, 주소 산itizer 토글, 잠시 끄는 UI 스위트를 모델링할 자리이며, 테스트를 지우지 않게 합니다.

테스트 플랜 vs 스킴만: 판단표

접근 빛나는 때 SSH에서의 운용성
커밋 .xctestplan + -testPlan Name 랩톱·빌더에서 재현. PR diff 높음—샤드마다 한 명령
스킴 기본 + -only-testing 스파이크·핫픽스 레인 중간—YAML의 문자열 팽창
서로 다른 스킴에 타깃 분리 무관한 앱이 섞인 모노레포 낮음—스킴 이중 유지

xcodebuild test 호출 청사진

호스트에서 검증한 Xcode에 DEVELOPER_DIR을 고정한 뒤, 명시적 결과 번들 경로와 job별 derived data로 테스트합니다. 셀프호스티드 러너나 사내 스케줄러와 상호연관이 쉬우려면 경로에 job ID를 넣으세요.

DEVELOPER_DIR=/Applications/Xcode.app xcodebuild test -workspace App.xcworkspace -scheme App -testPlan Nightly -destination 'platform=iOS Simulator,name=iPhone 16' -resultBundlePath "$CI_RESULTS/run-${CI_SHARD_ID}.xcresult" -derivedDataPath "$CI_DERIVED_DATA/${CI_SHARD_ID}"

공표할 수치: 알려진 플레이크는 기본 3배 로컬 재시도와 상한. 스위트당 p95가 18분을 넘기면 스케일 신호. 12코어급 M4 호스트에서는 경합이 벽시간을 지배하기 전에 xcodebuild test 2~4개 동시를 예산으로 잡으세요.

병렬 레인, 샤드, 큐

서로 다른 두 축이 있습니다. (a) 한 호스트에 여러 시뮬레이터 vs (b) destination이 하나씩인 자식 job. (a)는 비용엔 달콤하지만 CoreSimulator와 디스크 I/O를 비선형으로 늦춥니다. (b)는 싱가포르·미국 동부의 팬아웃에 가깝습니다. 샤드마다 CORESIMULATOR_HOST 접두어와 derived data, 자체 *.xcresult를 갖게 하세요. 병렬 xcodebuild 기사의 큐 규율을 재사용하되 테스트에 맞는 재시도 예산과 아카이브 job보다 20~35% 느슨한 샤드별 타임아웃이 필요합니다. XCTest 기동 변동은 실제입니다.

샤드 소유, 이름, 재실행

자식 job에 Git 이름과 독립된 안정적 CI_SHARD_ID를 주세요. 슬래시·이모지가 든 브랜치는 순진한 셸 글로브를 깨뜨립니다. 인덱스개수를 ID에 넣어 운영자가 YAML을 열지 않고도 로그를 읽을 수 있게: 예) test-3-of-12이 페이저 스크립트에선 shard-uuid보다 낫습니다. “실패한 테스트만 다시”를 누르면, 같은 DEVELOPER_DIR·resultBundlePath군을 유지하며 실패한 번들 ID만 -onlyTesting:에 재매핑합니다. 그래야 비슷한 이름의 둘째 xcresult가 버킷에 충돌하지 않습니다. “전체 녹색” 머지를 걸면 재실행 정책이 새 샤드 라벨을 허용하는지 애매하게 두지 말고 문서화하세요. 애매함이 플레이크를 정책 사고로 만듭니다.

단위는 몇 분, UI는 점심을 넘는 혼합 파이프라인이면, 느린 샤드에 빠른 피드백을 막지 마세요. 완성 전 xcresult스테이징 접두로 올리고, 마지막 샤드가 지난 뒤 “릴리스”로 승격하세요. 대시보드는 여전히 부분 번들의 벽시간 진행을 보여도 불완전 런과 main 브랜치의 녹색을 섞지 않게 합니다. DerivedData 기사의 분리와 잘 맞고, 동일 NVMe의 형제 샤드는 건드리지 않고 루트를 지울 수 있습니다.

xcresult 번들, 병합, 보관

xcresult는 자기완결 리포트입니다. Xcode가 버전마다 보장하지 않는 방식의 “디스크 병합”보다 샤드당 하나를 권장합니다. 하류는 퀄리티 대시에 JUnit·JSON으로 내보내면서, 인터랙티브 조사용 원본 xcresult도 아카이브하는 팀이 많습니다. 보존은 dSYM과 동일한 NVMe 정책에 맞추세요. 빌드와 테스트를 같은 호스트에서 돌릴 때는 dSYM·크래시 심볼리케이션의 보존 표를 봅니다.

디스크 위생: CoreSimulator는 1주일 섞인 샤드 뒤 GB 단위의 낡은 기기를 새며—분리 청소와 같은 창에 쓸어, 자정에 “종이엔 괜찮아 보이는데 위기”를 막으세요.

베어 메탈에서 XCTest 플레이크 조사 행렬

패턴 유력한 원인 첫 조치
6회에 1회 실패, UI·애니메이션 타임아웃 vs sim 렌더 변동 애니 안정. 타임아웃↑. 화면 녹화를 아티팩트에
한 샤드의 클래스 전부 실패 샤드별 env, 누락 시드 env 동등. 읽기 전용 공유 볼륨
월요일 뒤 모든 샤드가 느림 OS 패치 + 시뮬 런타임 변경 p95 재기준. Xcode/런타임 명시

NVMe, 1~2 TB 옵션, “CI를 무한대로?”

테스트 레인은 아카이브 전용보다 일시 I/O를 더 빨리 쌓습니다. 1~2 TB 임대 Mac이면 주간 스윕: 녹색 브랜치 7일 지난 xcresult 삭제, 기본 브랜치 30일, App Store Connect에 쓰는 태그 90일—규정팀이 더 촘촘히 잡을 수 있습니다. 서명 떨림이 테스트 떨림과 겹칠 땐 키체인·프로비저닝 가이드를 다시 봐 XCTest를 잘못 읽지 마세요.

이 런북을 시뮬레이터 테스트 기초, help의 액세스 패턴, 테스트·아카이브 함대를 나눌 때 노드 선택과 잇으세요.

FAQ: 헤드리스에서 테스트 플랜

질문 실무 답
디버그에 VNC가 필요하나요? 자동화에선 드뭄—아티팩트 쓰고, UI를 위한 VNC는 비상용.
Mac당 샤드 수는? UI 테스트: CPU 코어 / 3부터. p95가 1주 안정 뒤에만 올리세요.
한 플랜에 단위+UI? 소규모 앱은 괜찮음. 대형 리포는 레이어로 플랜을 나눠 핵심 경로의 신호를 앞당깁니다.

XCTest가 무거운 CI엔 Mac mini M4 베어 메탈

Mac mini M4 노드는 CoreSimulator와 하이퍼바이저 위가 아닌 NVMe의 결정론적 성능을 줍니다. XCTest p95가 보는 면이 바로 이 표면입니다. 홍콩 · 일본 · 한국 · 싱가포르 · 미국 리전에 테스트 빌더를 아카이브 옆에 두고, 같은 SSH 흐름, 아티팩트가 많은 조직엔 1~2 TB. 재시도 예산이 I/O·CPU에 걸릴 때가 아니고 테스트 플랜이 잘못된 게 아니라면, 가격에서 탄력적으로 호스트를 늘릴 수 있습니다.

테스트 수준의 클라우드 Mac 용량 추가

M4 · NVMe · 다지역