같은 iOS UI 테스트가 개발용 Mac에서는 통과하지만 클라우드 Mac으로 옮기면 간헐적으로 권한 알림에서 멈추는 경우가 있습니다. 이는 대개 비즈니스 코드가 무작위로 실패해서가 아니라 시뮬레이터에 이전 작업의 TCC 권한 상태가 남아 있기 때문입니다. 누군가 ‘허용’을 선택했다면 이후 테스트에서는 최초 권한 요청이 표시되지 않고, ‘허용 안 함’을 선택했다면 연락처나 사진에 의존하는 흐름이 곧바로 실패 경로로 진입합니다. 테스트를 안정화하는 핵심은 재시도 횟수를 늘리는 것이 아니라 각 테스트를 시작하기 전에 권한의 사전 조건을 명확하게 선언하는 것입니다.
권한 상태를 테스트 입력값으로 다루기
권한 테스트에서는 최소한 아직 결정되지 않음, 허용됨, 거부됨의 세 가지 상태를 다뤄야 합니다. 각 상태는 서로 다른 제품 흐름에 대응하므로 하나의 ‘앱 재설치’ 단계로 뭉뚱그려 처리해서는 안 됩니다.
| 목표 상태 | simctl 작업 | 검증할 동작 |
|---|---|---|
| 아직 결정되지 않음 | reset |
앱이 권한을 요청하고 시스템이 권한 알림을 표시함 |
| 허용됨 | grant |
다시 묻지 않고 기능이 바로 진행됨 |
| 거부됨 | revoke |
앱이 제한된 동작에 대한 안내 또는 설정 이동 안내를 표시함 |
먼저 현재 런타임이 대상 서비스를 지원하는지 확인합니다. 시스템 버전에 따라 제어할 수 있는 서비스가 다를 수 있으므로 경험에 의존해 서비스 이름을 고정한 뒤 바로 커밋해서는 안 됩니다.
xcrun simctl privacy help
xcrun simctl list devices available
자주 사용하는 서비스로는 contacts, photos, photos-add, location, microphone, calendar가 있습니다. 테스트 스크립트에서는 현재 Xcode에 포함된 simctl의 도움말 출력을 기준으로 삼아야 합니다.
권한 상태는 코드 저장소가 아니라 시뮬레이터 기기에 속합니다. 빌드 디렉터리만 정리해도 TCC 데이터는 삭제되지 않으며, 앱을 삭제한 뒤 다시 설치하는 방식 역시 신뢰할 수 있는 권한 초기화 방법으로 간주해서는 안 됩니다.
전용 시뮬레이터와 앱 준비하기
CI에서는 booted를 사용해 임의로 부팅된 기기를 가리키지 마세요. 병렬 작업이 동시에 같은 시뮬레이터를 찾으면 앱 설치, 실행, 권한 변경이 서로 덮어쓸 수 있습니다. 작업별로 명확한 UDID를 할당하고 이를 스크립트 인자로 전달하는 방식이 더 안정적입니다.
grant 또는 revoke가 실제 Bundle Identifier에 적용되려면 앱이 먼저 설치되어 있어야 합니다. Bundle Identifier도 Scheme 이름에서 추측하지 말고 빌드 결과물에서 직접 읽어야 합니다.
APP_PATH="$1"
UDID="$2"
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
"$APP_PATH/Info.plist")
xcrun simctl boot "$UDID" 2>/dev/null || :
xcrun simctl bootstatus "$UDID" -b
xcrun simctl install "$UDID" "$APP_PATH"
printf '%s
' "$BUNDLE_ID"
확장 프로그램을 테스트한다면 해당 확장 프로그램에는 별도의 Bundle Identifier가 있습니다. 각 확장 프로그램의 Info.plist를 따로 읽어야 하며, 모든 권한 명령에 메인 앱의 식별자를 사용해서는 안 됩니다.
스크립트로 세 가지 사전 상태 고정하기
상태 전환을 하나의 함수로 모아 두면 파이프라인 설정 곳곳에 분산하는 것보다 검토하기 쉽습니다. 권한을 변경하기 전에는 앱을 먼저 종료해 프로세스가 이전 상태를 계속 보유하거나 권한 알림을 표시 중인 상황을 방지합니다.
set -euo pipefail
UDID="$1"
BUNDLE_ID="$2"
SERVICE="$3"
STATE="$4"
xcrun simctl terminate "$UDID" "$BUNDLE_ID" 2>/dev/null || :
case "$STATE" in
undecided)
xcrun simctl privacy "$UDID" reset "$SERVICE" "$BUNDLE_ID"
;;
authorized)
xcrun simctl privacy "$UDID" grant "$SERVICE" "$BUNDLE_ID"
;;
denied)
xcrun simctl privacy "$UDID" revoke "$SERVICE" "$BUNDLE_ID"
;;
*)
printf 'Unknown permission state: %s
' "$STATE" >&2
exit 64
;;
esac
실행을 마친 뒤 앱을 시작하거나 테스트를 수행합니다. 예를 들어 연락처 권한이 허용된 시나리오에서는 먼저 스크립트를 호출한 다음 해당 테스트 클래스만 실행할 수 있습니다.
./set-permission.sh "$UDID" "$BUNDLE_ID" contacts authorized
xcodebuild test \
-scheme PermissionTests \
-destination "platform=iOS Simulator,id=$UDID" \
-only-testing:PermissionUITests/ContactsAuthorizedTests \
-resultBundlePath Artifacts/ContactsAuthorized.xcresult
reset은 거부 상태가 아니라 아직 결정되지 않은 상태로 되돌리는 작업입니다. 비즈니스 코드가 이 두 상태를 동일하게 처리한다면 테스트에서 이러한 설계 문제를 직접 드러내야 합니다.
시스템 권한 알림과 비즈니스 분기 분리하기
시스템 권한 알림은 시스템 UI이므로 버튼 탐색이 언어, 런타임 버전, 알림 표시 순서의 영향을 받기 쉽습니다. 모든 권한 테스트가 알림 버튼 클릭에 의존하게 만들지 마세요. 대부분의 테스트에서는 grant와 revoke로 비즈니스 분기를 검증하고, 최초 요청을 검증하는 테스트만 소수 유지하는 방식이 더 적절합니다.
최초 요청 테스트
먼저 reset을 실행한 후 앱을 시작하고 권한 요청을 발생시킵니다. 테스트에서는 알림이 나타나는지, 사용자가 선택한 뒤 앱이 예상 화면으로 이동하는지만 확인합니다. 시스템 UI를 조작해야 한다면 별도의 시스템 애플리케이션 객체를 사용하고 고정 좌표에 의존하지 않아야 합니다.
허용 및 거부 상태 테스트
이 두 유형의 테스트에서는 권한 알림이 나타나지 않아야 합니다. 허용 상태에서는 기능이 데이터를 정상적으로 읽는지 검증하고, 거부 상태에서는 앱이 권한을 반복 요청하거나 무한 로딩 상태에 머물지 않는지 검증합니다. 설정 이동 안내가 있다면 버튼과 설명만 확인하고 자동화 작업에서 실제로 시스템 설정을 변경하지는 않습니다.
위치 권한은 앱을 사용하는 동안의 허용과 지속적인 허용 등 비즈니스 의미도 구분해야 합니다. simctl privacy로 기본 상태를 준비할 수는 있지만 실제 기기의 모든 동작을 대신할 수는 없으므로 관련 결론은 실기기 검수에서도 확인해야 합니다.
격리, 증거 수집 및 실패 조사
각 테스트 클래스가 끝난 뒤 해당 서비스에 reset을 실행할 수 있지만, 더 중요한 원칙은 다음 테스트가 이전 테스트의 정리 성공 여부에 의존하지 않고 자체적으로 상태를 설정하게 하는 것입니다. 병렬 실행 시에는 시뮬레이터 한 대를 하나의 작업에만 할당해야 합니다. 동시 실행이 필요하다면 여러 기기를 생성하고 UDID를 공유하지 마세요.
실패 시에는 최소한 .xcresult, 테스트 로그, 시뮬레이터 시스템 로그, 현재 기기 목록을 보관해야 합니다. 예상한 상태가 적용되지 않았다면 다음 순서로 확인합니다.
- Bundle Identifier가 실제로 설치된 앱 또는 확장 프로그램에서 읽은 값인지 확인합니다.
- 현재 런타임이 해당 서비스 이름을 지원하는지 확인합니다.
- 상태를 변경하기 전에 앱이 종료되었는지 확인합니다.
- 대상 UDID가
xcodebuild에서 사용하는 기기와 일치하는지 확인합니다. - 다른 병렬 작업이 같은 시뮬레이터를 조작하고 있지 않은지 확인합니다.
- 테스트 코드가 이전 권한 결과를 캐시하고 있지 않은지 확인합니다.
‘시뮬레이터 전체 지우기’를 기본 해결책으로 삼지 마세요. 작업 시간이 크게 늘어날 뿐 아니라 상태 관리 결함을 가릴 수도 있습니다. 기기에서 설명하기 어려운 시스템 상태가 계속 나타날 때만 해당 기기를 다시 생성하고, 그전에 수집한 로그는 보관해야 합니다.
MiniRent의 클라우드 Mac에서 이러한 작업을 실행할 때 기기 모델과 노드는 작업 배치 방식에만 영향을 줍니다. 테스트 스크립트는 여전히 UDID, Bundle Identifier, 서비스 이름, 목표 상태를 사용해 환경을 완전하게 정의해야 하며, 현재 선택 가능한 구성은 콘솔에서 확인해야 합니다. 이렇게 하면 동일한 권한 회귀 테스트를 임시 디버깅 작업에서 재현할 수 있을 뿐 아니라 지속적 통합에도 안정적으로 포함할 수 있습니다.
자주 묻는 질문
simctl privacy가 모든 시스템 권한 팝업 테스트를 대체할 수 있나요?
아닙니다. 권한 상태 준비에는 적합하지만 실제 시스템 팝업의 문구, 버튼과 복귀 경로는 소수의 종단 간 UI 테스트로 확인해야 합니다.
권한 테스트마다 시뮬레이터를 지워야 하나요?
대부분 필요하지 않습니다. 앱을 종료한 뒤 해당 서비스와 번들 식별자에 reset, grant 또는 revoke를 적용하면 됩니다.
클라우드 Mac mini로 다음 개발 또는 빌드 작업 실행하기
두 가지 M4 구성, 네 가지 대여 기간, 현재 판매 중인 다섯 개 노드 중에서 선택하세요. 실제 사용 가능 여부는 콘솔의 실시간 응답을 기준으로 합니다.