xcresult 기반 XCTest 실행 시간 회귀 게이트 구축
원래 4초면 끝나던 XCTest가 점차 11초까지 늘어나도 파이프라인은 계속 정상으로 표시될 수 있으며, 팀은 대개 작업 대기열이 눈에 띄게 길어진 뒤에야 이를 알아차립니다. 전체 빌드 시간만으로는 이런 변화를 찾기 어렵습니다. 의존성 해석, 컴파일, 시뮬레이터 시작, 결과 기록 시간이 모두 섞여 있기 때문입니다. 더 신뢰할 수 있는 방법은 NUMACS 클라우드 Mac에서 실행하는 모든 테스트 작업의 .xcresult를 보관하고, 테스트 케이스별 실행 시간을 추출해 통제된 기준선과 비교하는 것입니다.
먼저 게이트가 측정할 대상을 정의하기
게이트가 답해야 할 질문은 “동일한 테스트가 지속적으로 느려지고 있는가”이지, “이번 작업이 이전 작업보다 몇 초 더 걸렸는가”가 아닙니다. 먼저 지표를 세 계층으로 나눕니다.
| 계층 | 지표 | 용도 |
|---|---|---|
| 테스트 케이스 | 단일 실행 시간 | 구체적인 회귀 식별 |
| 테스트 스위트 | 중앙값과 상위 백분위수 | 테스트 그룹 전체의 변화 추적 |
| CI 작업 | 시작부터 종료까지의 총시간 | 인프라 또는 컴파일 단계의 이상 감지 |
한 번의 결과만으로 병합을 차단하지 마세요. 짧은 테스트는 스케줄링 변동의 영향을 받기 쉽고, 긴 테스트는 네트워크, 애니메이션 또는 비동기 대기로 인해 시간이 크게 늘어날 수 있습니다. 실용적인 판정 방식은 “이중 임계값”입니다. 후보 값이 기준선 대비 상대 증가율과 절대 증가량을 모두 초과할 때만 재테스트 대상으로 지정합니다. 예를 들어 기준선이 2초일 때 20% 증가했더라도 실제 증가량이 0.1초에 불과하다면 실패로 처리해서는 안 됩니다. 반면 기준선이 40초이고 8초가 늘었다면 확인할 가치가 있습니다.
성능 게이트의 목적은 실행할 때마다 같은 수치를 얻는 것이 아니라, 반복 가능하고 원인을 규명할 수 있는 성능 저하를 조기에 식별하는 것입니다.
실행 환경을 고정하고 결과 번들 생성하기
먼저 Xcode 경로, Scheme, 실행 대상, 병렬 실행 정책을 고정합니다. 시뮬레이터 모델과 시스템 버전을 명시해야 하며, “현재 부팅된 기기”에 의존해서는 안 됩니다. 동일한 작업 안에서 동시 실행 수도 동적으로 변경하지 마세요.
set -euo pipefail
RESULT_DIR="$PWD/artifacts"
RESULT_BUNDLE="$RESULT_DIR/RegressionTests.xcresult"
mkdir -p "$RESULT_DIR"
rm -rf "$RESULT_BUNDLE"
xcodebuild test \
-workspace Example.xcworkspace \
-scheme ExampleTests \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=18.0' \
-resultBundlePath "$RESULT_BUNDLE" \
-parallel-testing-enabled NO
예시의 버전은 실행 환경을 구성하는 요소 중 하나일 뿐입니다. 실제 프로젝트에서는 검증을 마친 Xcode와 시뮬레이터 런타임을 고정해야 합니다. 통계에 포함하지 않는 워밍업 작업을 먼저 한 번 실행해 시뮬레이터가 완전히 시작되고 필요한 테스트 리소스가 디스크에 기록되도록 합니다. 정식 표본은 최소 세 번 실행해야 합니다. 테스트 자체의 분산이 크다면 의미가 없어질 정도로 임계값을 완화하는 대신 실행 횟수를 늘리세요.
xcresult에서 테스트 케이스 실행 시간 추출하기
최신 Xcode에서는 xcresulttool로 테스트 트리를 출력할 수 있습니다. 명령 인터페이스는 Xcode 버전에 따라 바뀔 수 있으므로 파서는 CI에서 사용하는 Xcode 버전과 함께 고정하고, 업그레이드 전에 저장된 결과 번들로 호환성을 테스트해야 합니다.
xcrun xcresulttool get test-results tests \
--path artifacts/RegressionTests.xcresult \
--format json > artifacts/tests.json
jq -r '
.. | objects
| select(.nodeType? == "Test Case" and .duration? != null)
| [.name, .duration] | @tsv
' artifacts/tests.json > artifacts/test-durations.tsv
비교 단계로 넘어가기 전에 TSV가 비어 있지 않은지, 테스트 수가 예상과 일치하는지 확인합니다. 빈 파일을 “회귀 없음”으로 간주해 통과시켜서는 안 됩니다. 일반적으로 명령 인터페이스가 변경되었거나, 테스트가 실행되지 않았거나, 파싱 조건이 더 이상 일치하지 않는다는 뜻입니다. 테스트 식별자에는 모듈, 클래스, 메서드가 포함되어야 합니다. 매개변수화된 테스트는 서로 다른 표본이 잘못 병합되지 않도록 매개변수 이름도 유지해야 합니다.
정규화할 때 원본 증거를 버리지 않기
실행 시간을 모두 초 단위로 변환하고 각 레코드에 commit, Xcode 버전, 실행 대상, 작업 번호를 추가합니다. 집계 파일은 비교에 편리하지만 원본 .xcresult도 작업 산출물로 보관해야 합니다. 이상이 발생했을 때 실패 정보, 활동 기록, 첨부 파일의 컨텍스트까지 확인할 수 있기 때문입니다.
직전 결과 대신 견고한 기준선 사용하기
기준선을 메인 브랜치의 가장 최근 실행 결과로 설정해서는 안 됩니다. 한 번의 느린 시작이 이후 판정을 오염시킬 수 있고, 비정상적으로 빨랐던 한 번의 실행이 대량의 오탐을 유발할 수도 있습니다. 더 안정적인 방법은 메인 브랜치에서 최근 성공한 여러 표본을 수집하고, 테스트별 중앙값과 함께 상위 백분위수 또는 중앙값 절대 편차를 기록하는 것입니다.
각 테스트에 다음 필드를 저장하는 것이 좋습니다.
{
"ExampleTests.testParsing": {
"median_seconds": 3.84,
"absolute_limit_seconds": 1.5,
"relative_limit": 0.25,
"sample_count": 9
}
}
후보 브랜치가 처음 임계값을 넘으면 해당 테스트 케이스 또는 소속 스위트만 다시 실행합니다. 재테스트 중앙값이 median_seconds + absolute_limit_seconds와 median_seconds × (1 + relative_limit)를 모두 초과할 때만 작업을 실패로 표시합니다. 새 테스트는 먼저 관찰 기간을 거치며, 표본이 부족한 동안에는 결과만 보고하고 병합 차단에는 사용하지 않습니다.
가장 흔한 오탐 회귀 배제하기
시뮬레이터 콜드 스타트는 가장 큰 노이즈 원인입니다. 테스트 데이터 초기화, 최초 글꼴 로드, 데이터베이스 테이블 생성도 첫 번째 실행을 느리게 만들 수 있으므로 워밍업을 적용하거나 명시적인 setUp 단계로 분리해야 합니다. 다음으로 테스트가 네트워크에 접근하는지, 실제 시간 경과를 기다리는지, 사용자 기본값을 공유하는지, 이전 테스트 케이스가 남긴 파일을 재사용하는지 확인합니다.
병렬 테스트는 CPU, 메모리, 디스크 경합 상태를 바꿀 수 있습니다. 개별 테스트 케이스의 기준선을 만드는 것이 목표라면 병렬 실행을 비활성화해야 합니다. 실제 파이프라인 처리량을 평가하는 것이 목표라면 worker 수를 고정하고 그 수를 기준선 차원에 포함하세요. Xcode 버전, 시스템 런타임 또는 하드웨어 구성이 다른 데이터는 직접 섞어서는 안 됩니다.
갑작스러운 성능 저하가 발생하면 다음 순서로 확인합니다.
- 테스트 수나 실행 대상이 변경되었는지
- 컴파일, 설치 또는 시뮬레이터 시작 대기가 발생했는지
- 시간 초과가 폴링이나 고정된 대기 시간에서 비롯되었는지
- 테스트 픽스처가 커졌거나 종료 후 정리되지 않았는지
- 여러 작업이 동일한 작업 디렉터리를 동시에 사용하고 있는지
기준선 변경을 검토 가능하게 만들기
기준선 파일은 버전 관리에 포함하되, 일반 테스트 작업에서는 읽기만 허용하고 자동으로 덮어쓰지 못하게 해야 합니다. 타당한 이유로 실행 시간이 늘어난 경우에는 별도의 작업에서 메인 브랜치의 안정적인 표본을 기반으로 차이를 생성해야 하며, 검토자는 이전 값, 새 값, 표본 수, 변경 사유를 확인할 수 있어야 합니다.
최종 보고서는 “새로운 회귀”, “복구됨”, “관찰 중”으로 구분하고 절대 증가량, 상대 증가율, 재테스트 결과를 함께 표시합니다. 이렇게 하면 한 번의 시뮬레이터 변동 때문에 개발 흐름을 막지 않으면서도 숨어 있는 성능 저하를 차단할 수 있습니다. 이 과정을 완료하면 팀은 쉽게 흔들리는 초시계 하나가 아니라 재현하고, 설명하고, 검토할 수 있는 XCTest 실행 시간 기준선 체계를 갖게 됩니다.
자주 묻는 질문
전체 xcodebuild 실행 시간만 기준으로 사용하면 왜 부정확한가요?
의존성 해석, 컴파일, 시뮬레이터 시작, 결과 저장 시간이 모두 섞이기 때문입니다. 테스트별 분석에는 xcresult의 개별 실행 시간이 필요합니다.
고정 시간과 백분율 중 어떤 임계값을 사용해야 하나요?
두 조건을 함께 사용하는 것이 안전합니다. 반복 실행 후에도 절대 증가량과 상대 증가율이 모두 한계를 넘을 때만 회귀로 판정합니다.
기준선을 갱신할 때 무엇을 보관해야 하나요?
기준선 파일, 해당 commit, Xcode 및 실행 대상 버전, 원본 xcresult, 변경 사유를 함께 보관해야 합니다.
NUMACS 클라우드 Mac
빌드 작업을 전용 물리 워크스테이션으로 옮기세요
두 가지 Apple Silicon 구성을 모두 전용 물리 머신으로 제공하며 가상 머신이 아닙니다. 일·주·월·분기 단위로 대여할 수 있고 싱가포르, 일본 도쿄, 한국 서울, 홍콩 노드를 지원합니다.