Runner 엔지니어링 기사

클라우드 Mac에서 simctl로 iOS 클립보드 회귀 테스트 만들기

클라우드 Mac에서 simctl로 iOS 클립보드 회귀 테스트 만들기

원격으로 iOS 테스트를 실행할 때 클립보드 관련 기능은 “로컬에서는 클릭 한 번으로 동작하지만 파이프라인에서는 검증할 수 없는” 사각지대에 빠지기 쉽습니다. 초대 코드 가져오기, 디버그 명령 붙여넣기, 여러 줄 텍스트 정리, 앱 내 복사 기능은 모두 시스템 클립보드에 의존하지만 일반적인 단위 테스트는 실제 진입 경로를 우회하는 경우가 많습니다. 더 안정적인 방법은 작업마다 독립된 시뮬레이터를 할당하고 simctl로 고정된 텍스트를 주입한 다음, UI 테스트에서 사용자가 볼 수 있는 붙여넣기 동작을 실행하는 것입니다.

먼저 테스트 범위 정의하기

이 방식은 일반 텍스트, Unicode, 공백 문자, 줄바꿈 처리를 검증하는 데 적합합니다. pbcopy를 임의의 바이너리 데이터를 주입하는 도구로 사용해서는 안 됩니다. 이미지, 파일 URL 또는 사용자 정의 UTI는 앱 내부 테스트 픽스처나 전용 테스트 호스트를 통해 제공해야 합니다.

먼저 실제로 보호해야 할 동작을 정리합니다.

자동화의 목표는 시스템 클립보드를 직접 읽는 것이 아니라, 사용자가 복사 또는 붙여넣기를 실행한 뒤 앱이 올바른 결과를 표시하는지 검증하는 것입니다.

작업마다 깨끗한 시뮬레이터 준비하기

병렬 작업에서 booted처럼 모호한 대상을 공유하면 안 됩니다. 두 작업이 동시에 pbcopy를 실행하면 나중에 기록된 내용이 앞서 기록된 내용을 덮어씁니다. 스케줄링 계층에서 고정된 UDID를 전달하고, DerivedData와 결과 번들에는 각각 독립된 디렉터리를 할당해야 합니다.

set -euo pipefail

: "${IOS_UDID:?IOS_UDID is required}"
JOB_ROOT="${PWD}/artifacts/${CI_JOB_ID:-local}"
mkdir -p "$JOB_ROOT"

xcrun simctl shutdown "$IOS_UDID" 2>/dev/null || true
xcrun simctl erase "$IOS_UDID"
xcrun simctl boot "$IOS_UDID"
xcrun simctl bootstatus "$IOS_UDID" -b

erase는 앱 컨테이너와 시뮬레이터 설정을 제거하므로 결정론적인 인수 테스트에 적합합니다. 준비 시간을 줄이기 위해 파이프라인에서 설치된 앱을 재사용해야 한다면 정리 설계를 생략해서는 안 됩니다. 최소한 테스트 대상 앱을 제거하고 클립보드를 비운 뒤 런타임 버전과 UDID를 기록해야 합니다.

격리 대상 권장 방식 공유할 때 발생하는 일반적인 문제
시뮬레이터 작업마다 UDID 하나 클립보드와 앱 상태가 서로 덮어쓰기됨
DerivedData 작업별 디렉터리 사용 동시 쓰기로 인해 빌드 결과가 불안정해짐
결과 번들 실행할 때마다 새 경로 생성 이전 첨부 파일이 이번 실패 기록에 섞임
텍스트 픽스처 버전 관리에 포함 입력은 바뀌었지만 테스트 코드는 바뀌지 않음

감사 가능한 텍스트 픽스처 주입하기

Shell에서 복잡한 Unicode 문자열을 직접 조합하지 마십시오. 픽스처를 UTF-8 파일로 저장해야 코드 리뷰에서 입력이 어떻게 변경되었는지 확인할 수 있습니다.

mkdir -p Tests/ClipboardFixtures

printf '第一行\n第二行:café\nemoji:🧪\n' \
  > Tests/ClipboardFixtures/multiline.txt

xcrun simctl pbcopy "$IOS_UDID" \
  < Tests/ClipboardFixtures/multiline.txt

픽스처는 정상 입력과 경계 입력을 모두 포함해야 하지만 모든 경우를 하나의 지나치게 긴 파일에 넣어서는 안 됩니다. 한 줄, 여러 줄, 앞뒤 공백, 결합 문자, 빈 내용 사례를 각각 관리하는 것이 좋습니다. 테스트가 실패했을 때 로그에는 픽스처 파일명, 바이트 수, 다이제스트만 기록하고 실제 클립보드 내용을 무조건 출력하지 마십시오. 민감한 데이터가 포함될 수 있기 때문입니다.

주입 후 디버깅 단계에서 한 번 읽어 보면 문제가 주입 측에서 발생했는지 앱 측에서 발생했는지 확인할 수 있습니다.

xcrun simctl pbpaste "$IOS_UDID" > "$JOB_ROOT/clipboard-before.txt"
cmp Tests/ClipboardFixtures/multiline.txt \
  "$JOB_ROOT/clipboard-before.txt"

프로덕션 파이프라인에서는 이 원문을 장기간 보관할 필요가 없습니다. 내용이 일치하는지 확인한 뒤 파일을 삭제하거나 shasum -a 256 결과만 저장하십시오.

실제 진입점으로 붙여넣기와 복사 실행하기

앱은 페이지가 로드될 때 클립보드를 몰래 읽는 대신 사용자가 알아볼 수 있는 붙여넣기 컨트롤을 제공해야 합니다. UI 테스트에서 해당 컨트롤을 누른 뒤 화면 결과를 확인하면 상호작용 경로를 검증하는 동시에 백그라운드 읽기를 정상 동작으로 잘못 판단하는 상황도 피할 수 있습니다.

func testPasteMultilineFixture() {
    let app = XCUIApplication()
    app.launchArguments += ["-ui-testing"]
    app.launch()

    let pasteButton = app.buttons["clipboard.paste"]
    XCTAssertTrue(pasteButton.waitForExistence(timeout: 10))
    pasteButton.tap()

    let editor = app.textViews["clipboard.editor"]
    XCTAssertTrue(editor.waitForExistence(timeout: 5))
    XCTAssertEqual(
        editor.value as? String,
        "第一行\n第二行:café\nemoji:🧪"
    )
}

시스템에서 붙여넣기 확인 UI를 표시한다면 테스트는 이를 내부 API로 우회하지 말고 상호작용의 일부로 처리해야 합니다. 컨트롤의 accessibility identifier는 안정적으로 유지해야 하며, 현지화된 버튼 텍스트에 의존해서는 안 됩니다.

복사 방향은 반대로 검증할 수 있습니다. UI 테스트에서 고정된 내용을 입력하고 복사를 누른 다음, 테스트 종료 후 pbpaste로 결과를 내보냅니다. 이를 통해 후행 줄바꿈, 서식 있는 텍스트의 일반 텍스트 변환 문제, 잘못된 공백 정리를 발견할 수 있습니다.

실행, 증거 수집 및 문제 해결

먼저 빌드를 완료한 뒤 테스트를 실행하면 “컴파일 실패”와 “상호작용 실패”가 같은 로그에 섞이는 상황을 줄일 수 있습니다.

xcodebuild build-for-testing \
  -scheme ClipboardApp \
  -destination "platform=iOS Simulator,id=$IOS_UDID" \
  -derivedDataPath "$JOB_ROOT/DerivedData"

xcodebuild test-without-building \
  -scheme ClipboardApp \
  -destination "platform=iOS Simulator,id=$IOS_UDID" \
  -derivedDataPath "$JOB_ROOT/DerivedData" \
  -resultBundlePath "$JOB_ROOT/ClipboardTests.xcresult"

실패할 경우 다음 순서로 확인합니다. UDID가 현재 작업에 속하는지, 시뮬레이터 부팅이 완료되었는지, 픽스처 해시가 일치하는지, 앱이 실제로 붙여넣기 컨트롤을 눌렀는지, 단언문이 최종 UI 상태를 읽는지 확인하십시오. 실패하자마자 테스트를 반복 실행하지 마십시오. 재실행하면 경쟁 조건을 덮어 현장 정보가 사라질 수 있습니다.

작업 종료 시 정리를 수행하도록 설정하되, 실패한 경우의 xcresult, 시뮬레이터 로그, 스크린샷은 보관할 수 있습니다. 클립보드 원문은 기본적으로 삭제하고 길이, 인코딩, 다이제스트, 픽스처 이름만 남겨야 합니다. 테스트가 사용자가 입력할 수 있는 내용을 다룬다면 로그 마스킹은 아카이브 전에 완료해야 합니다.

검사 항목을 파이프라인에 고정하기

안정적인 클립보드 회귀 작업은 최소한 다음 조건을 충족해야 합니다.

  1. UDID, DerivedData, 결과 디렉터리를 모두 작업별로 격리합니다.
  2. 시뮬레이터 런타임 버전을 기록합니다.
  3. 텍스트 픽스처를 UTF-8 파일로 버전 관리에 포함합니다.
  4. 테스트에서 사용자 동작을 통해 붙여넣기 또는 복사를 실행합니다.
  5. 빈 클립보드, Unicode, 줄바꿈을 각각 독립된 사례로 테스트합니다.
  6. 실패 시 UI 증거를 저장하되 클립보드 원문은 장기간 보관하지 않습니다.
  7. 작업 종료 후 시뮬레이터의 임시 상태를 정리합니다.

이 테스트의 가치는 시스템 클립보드 자체를 검증하는 데 있지 않습니다. 앱과 클립보드 사이의 계약을 반복해서 검증할 수 있는 입력과 출력으로 만드는 데 있습니다. 한 번의 실패를 구체적인 UDID, 픽스처 다이제스트, 테스트 단계, 결과 번들과 연결할 수 있다면 원격 문제 해결은 더 이상 사람의 설명에 의존하지 않습니다.

자주 묻는 질문

simctl pbcopy만으로 이미지 클립보드도 테스트할 수 있나요?

일반적인 바이너리 주입 수단으로 사용하면 안 됩니다. 텍스트는 pbcopy와 pbpaste로 검증하고, 이미지나 파일 URL, 사용자 정의 UTI는 앱 내부 픽스처 또는 전용 테스트 호스트로 주입해야 합니다.

병렬 CI 작업이 같은 iOS 시뮬레이터를 공유해도 되나요?

권장하지 않습니다. 작업마다 별도 UDID와 DerivedData, 결과 디렉터리를 사용해야 클립보드와 앱 컨테이너, 부팅 상태가 서로 덮어쓰이지 않습니다.

독점 물리 노드

클라우드 M4 Mac에서 다음 작업 실행

Runner M4 또는 Runner M4 Plus를 선택하고 프로젝트에 맞춰 노드, 대여 기간 및 스토리지 옵션을 결정하세요. 각 주문은 독립된 물리 머신에 배정되며 가상 머신이 아닙니다.

클라우드 Mac 지금 대여