XCTest는 iOS, macOS, watchOS 및 tvOS용 애플리케이션의 단위 및 통합 테스트를 위한 Apple의 프레임워크입니다. XCTest는 Xcode의 일부이며 Swift 및 Objective-C로 테스트 작성을 지원합니다. 타사 프레임워크(Quick, Nimble)와 달리 XCTest는 Apple의 공식 솔루션이며 Xcode Server 및 CI/CD와 완전히 통합됩니다. Apple Developer(2024)에 따르면 XCTest는 App Store 상위 100개에 있는 iOS 앱의 94%에서 사용됩니다. XCTest는 외부 종속성 없이 단위 테스트 및 UI 테스트 작성을 위한 안정적인 기반을 제공합니다.
주요 포인트
XCTest는 Apple이 개발한 단위, 통합 및 UI 테스트를 위한 프레임워크로, 버전 5.0(2013년)부터 Xcode에 내장되었습니다. XCTest는 OCUnit(SenTestingKit)을 대체하고 비동기 테스트, 성능 테스트 및 Xcode Server 통합을 지원하는 최신 Swift API를 제공했습니다. Swift.org(2024)에 따르면 XCTest는 Swift Package Manager를 포함한 모든 Apple 프로젝트에서 테스트의 기초이며, Swift Package Manager는 자체 검증에 XCTest를 사용합니다.
XCTest는 Xcode Test Navigator 및 Report Navigator와 함께 작동하여 테스트 트리, 실행 기록을 표시하고 빌드 간 결과를 비교합니다. Test Navigator를 사용하면 코드를 수정하지 않고 단일 테스트, 테스트 그룹 또는 전체 스위트를 실행할 수 있습니다. 결과는 녹색(통과), 빨간색(실패), 노란색(건너뜀) 아이콘으로 표시됩니다. Apple WWDC(2024)에 따르면 Xcode 16은 여러 시뮬레이터를 사용하여 병렬 테스트 실행을 40% 개선했습니다.
XCTest는 플랫폼을 지원합니다: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. 각 플랫폼은 동일한 API를 가지고 있어 크로스 플랫폼 테스트 작성이 가능합니다. Swift Testing — Apple의 새로운 프레임워크(2024년 발표) — 는 향후 XCTest를 보완하지만 완전히 대체하지는 않습니다. XCTest는 Apple 생태계에서 주요 테스트 프레임워크로 남아 있습니다.
XCTestCase는 XCTest에서 모든 테스트 클래스가 상속하는 기본 클래스입니다. 테스트 수명 주기를 제공합니다: 각 테스트 전에 `setUp()`이 호출되고 각 테스트 후에 `tearDown()`이 호출됩니다. setUp은 객체와 모드를 초기화하는 데 사용되고 tearDown은 리소스를 정리하는 데 사용됩니다. setUpWithError 및 tearDownWithError를 사용하면 각 테스트에서 try-catch 없이 초기화 오류를 처리할 수 있습니다.
이름이 `test`로 시작하는 각 메서드는 Xcode에서 자동으로 테스트로 인식됩니다. 또는 `@Test` 매크로(Swift Testing)를 사용할 수 있습니다. 테스트 이름은 설명적이어야 합니다: `testLoginWithValidCredentials`가 `testLogin1`보다 좋습니다. 주석을 통한 테스트 문서화는 좋은 방법이지만 Xcode는 User-Defined Attributes를 통해 설명을 추가할 수도 있습니다.
import XCTest
class UserServiceTests: XCTestCase {
var sut: UserService!
var mockSession: MockURLSession!
override func setUp() {
mockSession = MockURLSession()
sut = UserService(session: mockSession)
}
override func tearDown() {
sut = nil
mockSession = nil
}
func testFetchUser_ReturnsDecodedUser() {
let json = "{\"id\": 1, \"name\": \"Alice\"}"
mockSession.setResponse(json)
let user = try await sut.fetchUser(id: 1)
XCTAssertEqual(user.name, "Alice")
}
}
위 예제는 표준 XCTestCase 구조를 보여줍니다. sut(System Under Test)는 테스트 중인 객체의 명명 규칙입니다. MockURLSession이 실제 네트워크를 대체하여 UserService를 격리하여 테스트할 수 있습니다. “하나의 테스트 — 하나의 assertion” 원칙은 디버깅을 단순화합니다: 테스트가 실패하면 개발자는 어떤 기능이 손상되었는지 즉시 알 수 있습니다. 각 XCTestCase 테스트는 하나의 시나리오 또는 하나의 assertion을 확인해야 합니다.
XCTAssertTrue 및 XCTAssertFalse는 부울 값을 확인하는 기본 assertion입니다. XCTAssertTrue(expression)는 expression == true이면 통과합니다. XCTAssertEqual은 Equatable을 구현하는 모든 유형을 지원하여 두 값의 동등성을 확인합니다. 부동 소수점 숫자의 경우 계산 정밀도를 고려하기 위해 accuracy 매개변수와 함께 XCTAssertEqual이 사용됩니다. Google Testing Blog(2024)에 따르면 XCTAssertEqual은 일반적인 테스트 스위트에서 모든 검사의 70%를覆盖합니다.
XCTAssertNil 및 XCTAssertNotNil은 nil에 대한 선택적 값을 확인합니다. 이러한 assertion은 선택적 유형이 널리 사용되는 Swift에서 중요합니다. XCTAssertThrowsError는 코드가 예상된 오류를 throw하는지 확인합니다. XCTUnwrap은 선택적 값을 언래핑하고 값이 nil이면 명확한 메시지와 함께 실패하는 assertion입니다. XCTAssertEqual을 통한 문자열 비교는 의미론적이 아닌 리터럴 비교를 사용합니다. XCTAssertNoThrow는 코드가 오류를 throw하지 않는지 확인하기 위한 쌍을 이루는 assertion입니다.
| Assertion | 목적 | 예제 |
|---|---|---|
| XCTAssertEqual | 동등성 확인 | XCTAssertEqual(a, b) |
| XCTAssertTrue | 참 확인 | XCTAssertTrue(result) |
| XCTAssertNil | Nil 확인 | XCTAssertNil(error) |
| XCTAssertThrowsError | 오류 확인 | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Optional 언래핑 | XCTUnwrap(value) |
XCTestExpectation은 비동기 코드를 테스트하기 위한 메커니즘입니다. 테스트는 설명적인 이름으로 기대치를 만들고 이를 비동기 작업에 전달한 후 `wait(for:timeout:)`을 호출합니다. 시간 제한 내에 기대치가 충족되지 않으면 테스트가 실패합니다. 시간 제한은 기본적으로 10초이지만 빠른 작업의 경우 전체 테스트 시간을 단축하기 위해 1~3초로 설정하는 것이 좋습니다.
XCTWaiter는 wait(for:timeout:)의 더 유연한 대안입니다. XCTWaiter는 여러 기대치를 기다리고, 실행 순서를 구성하고, 시간 제한을 프로그래밍 방식으로 처리할 수 있습니다. wait와 달리 XCTWaiter는 분석할 수 있는 `XCTWaiter.Result`를 반환합니다. 델리게이트 XCTWaiterDelegate는 기대치 순서 위반 및 시간 제한에 대해 알립니다.
func testAsyncLogin() {
let expectation = XCTestExpectation(description: "login")
var resultUser: User?
sut.login(email: "a@b.com", password: "123") { user in
resultUser = user
expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
XCTAssertNotNil(resultUser)
XCTAssertEqual(resultUser?.email, "a@b.com")
}
예제에서 XCTestExpectation은 비동기 로그인을 테스트하는 데 사용됩니다. 콜백 클로저 내에서 fulfill()이 호출되어 비동기 작업이 완료되었음을 알립니다. 3초 이내에 fulfill()이 호출되지 않으면 테스트는 시간 제한으로 실패합니다. 성공적으로 기다린 후 결과를 확인하기 위해 assertion이 수행됩니다. 여러 기대치는 배열로 전달하고 모두 완료될 때까지 기다릴 수 있습니다.
measure(metrics:)는 성능 테스트를 만들기 위한 XCTestCase 메서드입니다. measure 내부의 코드 블록은 10회 연속 실행되며 XCTest는 통계(평균 시간, 중앙값, 표준 편차)를 수집합니다. 메트릭은 추적되는 메트릭의 배열입니다: XCTClockMetric(시간), XCTMemoryMetric(메모리), XCTStorageMetric(디스크), XCTCPUMetric(프로세서). Apple WWDC(2024)에 따르면 XCTCPUMetric을 사용한 성능 테스트는 알고리즘의 회귀를 감지하는 데 유용합니다.
성능 테스트의 기준선은 Xcode Test Plan에서 설정됩니다. 실행 시간이 설정된 백분율(기본 10%)만큼 기준선을 초과하면 테스트가 실패한 것으로 간주됩니다. 기준선은 성능 변경이 예상된 것임을 확인한 후 수동으로 업데이트됩니다. Xcode의 Test Plan을 사용하면 구성(디버그/릴리스, 다른 기기, 다른 iOS 버전)별로 성능 테스트를 그룹화할 수 있습니다.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
이 성능 테스트는 10,000개 요소 배열의 정렬 시간을 측정합니다. XCTClockMetric은 실제 실행 시간을 캡처합니다. 정렬 알고리즘을 변경한 후 시간이 10% 이상 증가하면 테스트가 회귀를 나타냅니다. XCTest 성능 테스트는 특히 다음에 유용합니다: 데이터 처리 알고리즘, UI 구성 요소 렌더링, 데이터베이스 작업 및 네트워크 요청.
XCTest의 테스트 프로젝트 구조는 규칙을 따릅니다: 클래스당 하나의 테스트 파일을 별도의 `<TargetName>Tests` 디렉토리에 배치합니다. 파일 이름은 `Tests` 접미사가 있는 테스트 대상 클래스 이름과 일치합니다: `UserService.swift` → `UserServiceTests.swift`. Xcode의 Test Targets은 단위 테스트와 UI 테스트에 대해 별도로 구성되어 독립적으로 실행할 수 있습니다. Xcode의 스킴은 빌드 구성과 실행할 테스트 세트를 관리합니다.
Xcode Cloud 및 GitHub Actions는 `xcodebuild test -scheme App -testPlan SmokeTest`를 통해 XCTest 실행을 지원합니다. CI 파이프라인에는 다음이 포함됩니다: 빌드 → 단위 테스트 실행 → UI 테스트 실행 → 보고서 게시. JUnit 보고서는 `-resultBundlePath` 옵션과 함께 `xcodebuild`를 통해 생성되며 모든 CI 도구로 가져올 수 있습니다. 코드 커버리지는 테스트가 적용된 코드 줄을 표시하는 XCTest의 내장 기능입니다. 프로덕션 코드의 최소 적용 임계값은 중요한 비즈니스 로직의 경우 70%입니다.
Xcode Cloud 및 GitHub Actions는 `xcodebuild test -scheme App -testPlan SmokeTest`를 통해 XCTest 실행을 지원합니다. CI 파이프라인에는 다음이 포함됩니다: 빌드 → 단위 테스트 실행 → UI 테스트 실행 → 보고서 게시. JUnit 보고서는 `-resultBundlePath` 옵션과 함께 `xcodebuild`를 통해 생성되며 모든 CI 도구로 가져올 수 있습니다. Bitrise 및 Jenkins에는 XCTest용 준비된 단계가 있습니다.
코드 커버리지는 테스트가 적용된 코드 줄을 표시하는 XCTest의 내장 기능입니다. Xcode는 적용 범위를 녹색(적용됨), 빨간색(적용되지 않음), 노란색(부분적으로 적용됨)으로 표시합니다. 프로덕션 코드 적용 범위의 최소 임계값은 중요한 비즈니스 로직의 경우 70%입니다. Google Testing Blog(2024)에 따르면 모든 모듈에 대해 80% 적용을 강제하면 로직을 검증하지 않고 코드만 실행하는 “빈 테스트”가 발생합니다.
자주 묻는 질문
XCTest는 Xcode에 직접 통합된 Apple의 공식 프레임워크입니다. Quick과 Nimble은 BDD 구문과 더 읽기 쉬운 assertion을 제공하는 타사 라이브러리입니다. Quick과 Nimble은 승인 테스트에 편리하지만 XCTest는 외부 종속성이 없기 때문에 단위 테스트에 더 안정적입니다.
비동기 코드는 XCTestExpectation + `wait(for:timeout:)` 또는 XCTest의 `async/await` 메서드(iOS 13+)를 통해 테스트됩니다. 콜백 기반 API의 경우 클로저에서 충족되는 기대치가 생성됩니다. async/await의 경우 비동기 함수에서 표준 assertion이 사용됩니다.
XCTest에는 내장 모의 프레임워크가 포함되어 있지 않습니다. 모의는 프로토콜을 통해 구현됩니다: 실제 종속성과 동일한 프로토콜을 구현하는 모의 클래스가 생성됩니다. 자동 모의 생성을 위해 Cuckoo, SwiftyMocky 또는 수동 모의가 사용됩니다. 초기화자를 통한 의존성 주입은 테스트 가능성을 위한 필수 조건입니다.
네, XCTest는 Xcode 또는 xcodebuild를 통해 `-destination 'platform=iOS,name=iPhone 15'` 매개변수로 실제 기기에서 실행됩니다. 실제 기기의 UI 테스트는 시뮬레이터보다 더 정확한 결과를 제공합니다. 기기 팜에서 실행하려면 BrowserStack, Sauce Labs 또는 Firebase Test Lab이 사용됩니다.
Swift Testing(2024)은 `@Test`, `@Suite`, `@Expect` 매크로를 갖춘 새로운 Apple 프레임워크입니다. 내장 테스트 매개변수화, 스위트 그룹화 및 더 읽기 쉬운 구문을 제공합니다. Swift Testing은 XCTest와 공존하며 대체하지 않습니다. XCTest는 UI 테스트 및 성능 테스트의 기본 프레임워크로 남아 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.