동일한 XCUITest 세트가 로컬에서는 연속으로 통과하지만 클라우드 Mac으로 옮긴 뒤 간헐적으로 버튼을 찾지 못하거나 대기 시간이 초과되고 시작 화면에서 멈춘다면, 단순히 “머신이 느려서” 생기는 문제인 경우는 드뭅니다. 실행 대상이 달라지거나 이전 테스트가 상태를 남기고, 고정 시간 대기를 사용하거나 실패 후 충분한 증거를 보존하지 않는 것이 더 흔한 원인입니다. 안정화 작업은 먼저 입력을 고정하고 암묵적 의존성을 제거한 다음, 마지막으로 재실행을 검토하는 순서로 진행해야 합니다.
매 실행의 입력부터 고정하기
Xcode, 테스트 계획, 시뮬레이터 대상, 빌드 디렉터리를 명령에 명시하고 GUI에 남아 있는 이전 선택값에 의존하지 마세요. 클라우드 Mac은 작업을 장기간 실행할 수 있고 하나의 작업 디렉터리를 여러 파이프라인이 사용할 수도 있으므로, 실행마다 독립된 DerivedData와 결과 번들을 사용해야 합니다.
RUN_ID="${CI_RUN_ID:-local-$(date +%s)}"
ARTIFACTS="$PWD/artifacts/$RUN_ID"
DERIVED="$PWD/.derived/$RUN_ID"
mkdir -p "$ARTIFACTS" "$DERIVED"
xcodebuild test \
-workspace Sample.xcworkspace \
-scheme SampleUITests \
-testPlan Smoke \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-derivedDataPath "$DERIVED" \
-resultBundlePath "$ARTIFACTS/Smoke.xcresult" \
-parallel-testing-enabled NO
불안정한 테스트를 처음 정비할 때는 병렬 테스트를 끄고 직렬 실행 기준선을 먼저 확보하세요. 기준선이 안정된 뒤 테스트 그룹별로 병렬 실행을 활성화해야 합니다. 그렇지 않으면 여러 시뮬레이터와 테스트 데이터, 백그라운드 작업이 동시에 바뀌어 장애 원인이 테스트 자체인지 스케줄링인지 판단하기 어렵습니다.
시작 전 최소 점검 수행하기
실행 전에 xcodebuild -version을 기록하고 xcrun simctl list devices available로 대상이 실제로 존재하는지 확인하세요. 시스템 조건을 생략한 채 기기 이름만 지정해서는 안 되며, 서로 다른 Runner가 동일한 DerivedData 디렉터리를 공유해서도 안 됩니다.
고정 대기 대신 관찰 가능한 상태 사용하기
Thread.sleep은 간단해 보이지만, 실제로는 “화면 준비가 완료됨”이라는 조건을 “몇 초가 지남”으로 잘못 표현한 것입니다. 네트워크 응답이 빠르면 시간을 낭비하고, 애니메이션이나 디스크 작업이 조금만 느려져도 너무 일찍 대기가 끝납니다.
let app = XCUIApplication()
app.launchArguments += ["--ui-testing", "--reset-state"]
app.launch()
let continueButton = app.buttons["onboarding.continue"]
let ready = continueButton.waitForExistence(timeout: 12)
XCTAssertTrue(ready, "Continue button did not appear")
XCTAssertTrue(continueButton.isHittable)
continueButton.tap()
요소는 현지화된 문구나 뷰 계층 구조가 아니라 안정적인 accessibility identifier로 찾아야 합니다. 버튼이 이미 존재하지만 탭할 수 없다면 오버레이, 애니메이션, 스크롤 위치를 추가로 점검해야 하며, 단순히 제한 시간을 늘려서는 안 됩니다.
제한 시간은 장애를 판별하는 경계이지 성능 보장이 아닙니다. 정상적인 변동은 수용하되 실제로 멈췄을 때는 최대한 빨리 실패하고 당시 상태를 남길 수 있어야 합니다.
모든 테스트를 알려진 상태에서 시작하기
간헐적 실패는 이전 테스트에서 비롯되는 경우가 많습니다. 시작 안내 화면이 이미 건너뛰어졌거나 테스트 데이터가 남아 있고, 팝업이 한 번만 표시되거나 백그라운드 프로세스가 종료되지 않았을 수 있습니다. 가장 안정적인 방법은 테스트용 시작 인수가 전달되었을 때 앱이 전용 초기화 로직을 실행하도록 하고, 각 테스트 ID마다 고유한 데이터를 생성하는 것입니다.
다음과 같이 상태의 경계에 따라 계층별로 처리할 수 있습니다.
| 상태 유형 | 권장 방식 | 권장하지 않는 방식 |
|---|---|---|
| 앱 환경설정 | 시작 인수로 테스트 전용 초기화 실행 | 설치 후 상태가 항상 비어 있다고 가정 |
| 로컬 데이터베이스 | 고정 픽스처를 가져오거나 테스트 DB 재구축 | 이전 테스트가 기록한 데이터에 의존 |
| 서버 측 데이터 | 이번 실행의 고유 네임스페이스 사용 | 여러 작업이 고정 레코드를 공유 |
| 시스템 권한 | 테스트 스위트 실행 전에 일괄 준비하고 검증 | 임의의 테스트 안에서 즉석 처리 |
| 앱 프로세스 | 테스트 종료 후 명시적으로 종료 | 충돌 후 자동 복구된다고 가정 |
테스트 스위트 수준의 정리 과정에서는 앱을 종료할 수 있습니다. 제거는 최초 설치 흐름을 실제로 검증해야 할 때만 수행하세요. 시뮬레이터 전체를 자주 초기화하면 실행 시간이 늘어나고, 환경 문제를 “정리 후 통과”로 감출 수도 있습니다.
xcrun simctl terminate booted "$APP_BUNDLE_ID" 2>/dev/null || true
재실행을 분류 도구로 활용하기
자동 재실행을 통과 조건의 대체 수단으로 사용해서는 안 됩니다. 합리적인 정책은 첫 실패 후 한 번만 재실행하고 attempt-1.xcresult와 attempt-2.xcresult를 각각 보존하는 것입니다. 두 번 모두 실패하면 우선 재현 가능한 결함으로 처리하세요. 첫 실행은 실패하고 두 번째 실행은 통과했다면 불안정한 테스트로 표시하고 대기 조건, 공유 상태, 리소스 경합을 계속 조사해야 합니다.
최종 종료 코드만 보존하지 않기
실패할 때마다 최소한 다음 항목을 수집하세요.
- 독립된 결과 번들과 테스트 로그
- 실패 단계의 스크린샷과 UI 계층 구조
- Xcode 버전, 대상 시뮬레이터, 시작 인수
- 이번 실행의 식별자, 테스트 이름, 시작 및 종료 시간
- 앱 종료 상태와 관련 시스템 로그 일부
동시 실행 작업이 결과를 덮어쓰지 않도록 결과 번들 경로에는 반드시 실행 식별자를 포함해야 합니다. 로그에 포함된 토큰, 자격 증명, 비즈니스 데이터는 업로드 전에 마스킹해야 합니다.
단일 테스트 재현부터 안정성 검증까지
먼저 -only-testing으로 대상 테스트를 연속 실행해 관련 없는 변수를 줄이세요. 그다음 해당 테스트 클래스를 실행하고, 마지막으로 전체 테스트 스위트로 돌아갑니다. 전체 스위트에서만 실패한다면 대개 실행 순서 의존성, 공유 데이터, 리소스 경합이 있다는 뜻입니다.
xcodebuild test \
-workspace Sample.xcworkspace \
-scheme SampleUITests \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-only-testing:SampleUITests/CheckoutTests/testSubmit \
-parallel-testing-enabled NO
검증할 때는 “한 번 통과”했다는 사실만 확인해서는 안 됩니다. 여러 차례 연속 실행해 고정된 순서 의존성이 없는지 확인하고, 지나치게 긴 대기 상한 때문에 전체 실행 시간이 눈에 띄게 증가하지 않았는지도 점검해야 합니다. 병렬 테스트를 다시 활성화한 뒤에는 서로 다른 샤드가 동일한 테스트 레코드나 동일한 출력 디렉터리를 사용하지 않는지 추가로 검증하세요.
최종 목표는 실패율을 재시도 뒤에 숨기는 것이 아니라, 모든 실행에 결정적인 입력과 명확한 대기 조건, 격리된 상태, 완전한 증거를 갖추는 것입니다. 이 네 가지를 충족해야 클라우드 Mac의 XCUITest가 “가끔 통과하는 테스트”에서 병합 및 릴리스 여부를 판단할 수 있는 신뢰도 높은 엔지니어링 신호로 바뀝니다.
자주 묻는 질문
실패한 XCUITest를 자동으로 다시 실행해도 되나요?
분류를 위한 재실행은 한 번만 허용하는 편이 좋습니다. 최초 실패와 재실행 결과를 모두 보존하고, 재실행에서만 통과한 테스트는 불안정 항목으로 따로 관리해야 합니다.
UI 테스트에서 고정 sleep을 피해야 하는 이유는 무엇인가요?
고정 시간은 화면 준비 완료를 보장하지 못하고 빠르게 끝난 실행도 불필요하게 지연시킵니다. 요소 존재, 조작 가능 여부, 예상 문구처럼 관찰 가능한 상태를 제한 시간과 함께 기다려야 합니다.
다음 개발 파이프라인을 위한 클라우드 Mac 선택
고정 구성 3종과 아시아 노드 4곳을 비교하고, 실제 워크로드에 맞춰 대여 기간을 선택하세요. 각 대여에는 가상 머신이 아닌 전용 물리 장비 1대가 제공됩니다.