로그인 화면을 막 리팩터링한 뒤 로컬에서 비밀번호 대체 경로까지 검증했지만, 생체 정보가 등록되지 않은 경우 더 이상 다음 단계로 진행할 수 없다는 사실은 아무도 발견하지 못했습니다. 테스트 환경에서 오래된 시뮬레이터를 재사용하고 나서야 흐름이 간헐적으로 멈췄습니다. 이런 문제의 원인은 알고리즘이 아니라 테스트에서 인증 상태를 통제하지 않은 데 있습니다. 클라우드 Mac을 사용하면 시뮬레이터 상태, 인증 이벤트, 정리 절차를 파이프라인으로 고정하여 모든 커밋에서 동일한 분기들을 검증할 수 있습니다.
먼저 자동화 범위 정하기
simctl biometric은 등록, 미등록, 일치, 불일치 이벤트를 시뮬레이션할 수 있어 애플리케이션이 결과를 받은 뒤의 화면과 상태 전환을 검증하는 데 적합합니다. 하지만 센서나 실제 등록 과정은 검증할 수 없으며, 기기 수준의 보안성을 입증하지도 못합니다. 테스트 목표는 다음 네 가지로 한정해야 합니다.
| 시나리오 | 주입 상태 | 애플리케이션의 예상 동작 |
|---|---|---|
| 인증 성공 | enroll + match | 보호된 페이지로 이동 |
| 사용자 거부 | enroll + nonmatch | 현재 페이지를 유지하고 재시도 허용 |
| 미등록 | unenroll | 실행 가능한 대체 경로 표시 |
| 기능 사용 불가 | 테스트 대역이 오류 반환 | 팝업을 반복 표시하지 않고 사용자 입력 유지 |
시뮬레이터 테스트가 입증하는 것은 “애플리케이션이 시스템 결과를 올바르게 처리한다”는 점이지, “생체 정보 자체가 신뢰할 수 있다”는 점이 아닙니다.
실제 기기를 이용한 인수 테스트도 계속 필요하지만, 모든 비즈니스 분기를 수동 작업에 의존할 필요는 없습니다.
시스템 인증을 교체 가능한 경계로 감싸기
뷰 컨트롤러가 인증 컨텍스트를 직접 생성하면 단위 테스트는 시스템 팝업을 기다릴 수밖에 없습니다. 더 안정적인 방법은 작은 프로토콜을 정의하고, 프로덕션 구현에서는 시스템 프레임워크를 호출하며 테스트 구현에서는 정해진 결과를 반환하는 것입니다.
import LocalAuthentication
protocol BiometricAuthenticating {
func authenticate(reason: String) async throws -> Bool
}
struct SystemBiometricAuthenticator: BiometricAuthenticating {
func authenticate(reason: String) async throws -> Bool {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
error: &error
) else {
throw error ?? LAError(.biometryNotAvailable)
}
return try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: reason
)
}
}
비즈니스 계층은 BiometricAuthenticating만 주입받습니다. 단위 테스트에서는 오류 매핑을 빠짐없이 검증하고, UI 테스트에는 소수의 엔드투엔드 사례만 남겨 시스템 패널이 나타난 뒤 성공, 실패, 대체 버튼이 실제로 올바른 페이지에 연결되는지 확인합니다. 이렇게 하면 시스템 안내 문구가 버전에 따라 달라지더라도 핵심 판정이 깨지기 쉬운 전체 문구 일치에 의존하지 않습니다.
시뮬레이터를 고정하고 인증 상태 제어하기
먼저 현재 도구가 지원하는 매개변수를 확인하여 다른 실행 환경의 명령 형식을 그대로 복사하지 않도록 합니다.
xcrun simctl help biometric
xcrun simctl list devices available
파이프라인에서는 모호한 booted 선택자를 사용하지 마세요. 병렬 작업이 동시에 기기를 시작하면 잘못된 인스턴스가 선택될 수 있습니다. 각 작업이 고유한 UDID를 생성하거나 할당받고, 모든 명령에서 동일한 값을 사용해야 합니다.
set -euo pipefail
UDID="${SIMULATOR_UDID:?missing simulator udid}"
xcrun simctl boot "$UDID" 2>/dev/null || true
xcrun simctl bootstatus "$UDID" -b
xcrun simctl biometric "$UDID" unenroll
xcrun simctl biometric "$UDID" enroll
테스트를 시작한 뒤에는 인증 패널이 실제로 준비될 때까지 기다린 다음 이벤트를 보내야 합니다. 2초처럼 고정된 대기 시간은 부하가 달라지면 쉽게 실패합니다. 테스트 빌드가 인증을 트리거하기 직전에 AUTH_PROMPT_READY 같은 자체 마커를 출력하게 하고, 외부 컨트롤러가 제한 시간이 설정된 로그 스트림에서 이 마커를 감지한 후 다음 명령을 실행하도록 구성할 수 있습니다.
xcrun simctl biometric "$UDID" match face
xcrun simctl biometric "$UDID" nonmatch face
실행 환경에 따라 허용되는 생체 인증 유형 매개변수가 다를 수 있습니다. 따라서 최종 명령은 해당 환경의 help biometric 출력에 맞춰야 하며, 호환되지 않는 경우 테스트 도중이 아니라 환경 사전 점검 단계에서 실패하도록 해야 합니다.
테스트 케이스를 서로 격리하기
생체 정보 등록 상태는 개별 테스트 메서드가 아니라 시뮬레이터에 속합니다. 앞선 테스트가 남긴 enroll 상태는 다음의 “미등록” 테스트를 오염시킬 수 있습니다. 각 시나리오에서 사전 상태를 명시적으로 설정하고, 테스트가 끝나면 복원하는 것이 좋습니다.
cleanup() {
xcrun simctl biometric "$UDID" unenroll 2>/dev/null || true
xcrun simctl shutdown "$UDID" 2>/dev/null || true
}
trap cleanup EXIT
동일한 기기 세트를 공유하지 않기
한 대의 LemonVM 클라우드 Mac에서 여러 테스트 샤드를 병렬로 실행할 때는 작업마다 별도의 --set 디렉터리를 지정하거나 서로 다른 UDID를 미리 할당하세요. 기기 세트, DerivedData, 결과 번들은 모두 작업 식별자별 디렉터리를 사용해야 한 작업이 다른 작업의 기기를 삭제하는 일을 방지할 수 있습니다.
시스템 문구를 단언하지 않기
시스템 대화상자는 시스템 버전, 언어, 기기 기능에 따라 달라집니다. UI 테스트에서는 시스템 패널, 애플리케이션 자체 버튼, 최종 비즈니스 상태를 확인해야 하며 시스템 안내 문구를 한 글자씩 비교해서는 안 됩니다. 오류 유형의 정확한 매핑은 프로토콜 테스트 대역을 사용하는 단위 테스트에서 검증합니다.
실패 증거와 통과 기준 체크리스트 마련하기
실패 시에는 최소한 테스트 결과 번들, 애플리케이션 로그, 대상 UDID, 시스템 버전, 시뮬레이터 모델, 테스트 시작 전 등록 상태를 보관해야 합니다. 실패 화면 한 장만으로는 이벤트를 너무 일찍 보냈는지, 잘못된 기기를 선택했는지, 애플리케이션이 콜백을 처리하지 않았는지 판단하기 어렵습니다.
커밋 전에는 다음 체크리스트에 따라 검증할 수 있습니다.
- 인증 구현을 주입할 수 있으며 비즈니스 계층이 시스템 컨텍스트에 직접 의존하지 않습니다.
- 성공, 거부, 미등록, 사용 불가의 네 경로에 모두 명확한 단언이 있습니다.
- 병렬 작업마다 독립적인 UDID를 사용하며 전역
booted를 사용하지 않습니다. - 이벤트 전송 전에 관찰 가능한 준비 마커와 제한 시간이 설정되어 있습니다.
- 각 테스트가 등록 상태를 명시적으로 설정하고 종료 시 정리 절차를 실행합니다.
- 실패 산출물에 결과 번들, 로그, 시뮬레이터 정보가 포함됩니다.
- 릴리스 절차에 실제 기기를 이용한 최소 인수 테스트가 계속 포함됩니다.
이 구조의 가치는 단순히 명령을 몇 개 더 실행하는 데 있지 않습니다. 보이지 않는 전제였던 “인증 상태”를 테스트 입력으로 바꾸는 데 있습니다. 입력, 기기, 타이밍을 모두 기록할 수 있다면 생체 인증 회귀 검증은 간헐적인 수동 점검에서 반복 가능한 엔지니어링 절차로 전환됩니다.
자주 묻는 질문
simctl biometric이 실제 기기의 생체 인증 테스트를 대체할 수 있나요?
대체할 수 없습니다. 앱의 상태 분기, 안내 화면과 대체 인증 경로는 검증할 수 있지만 센서 품질과 실제 등록 과정은 확인하지 못하므로 출시 전 실제 기기 검증이 필요합니다.
match 또는 nonmatch 이벤트가 가끔 전달되지 않는 이유는 무엇인가요?
인증 패널이 나타나기 전에 이벤트를 보내거나 여러 작업이 같은 시뮬레이터를 공유하면 발생합니다. 관측 가능한 준비 신호를 기다리고 작업마다 별도 UDID를 사용해야 합니다.
작업 기간에 맞춰 LemonVM 클라우드 Mac 선택
Lemon M4와 Lemon M4 Pro는 싱가포르, 도쿄, 서울, 홍콩, 미국 서부에 제공되며 일간, 주간, 월간, 분기별 결제를 지원합니다.