Xcode에서 각 시나리오마다 자체 결과와 재실행 제어 기능을 제공하려면 루프 기반 XCTest 단위 테스트를 Swift Testing의 매개변수화된 @Test로 옮기세요. 이 안내에서는 로컬 타일 레이아웃 계산을 사용해 마이그레이션 방법과 결과 보고 동작을 확인합니다.
XCTest 루프는 왜 각 시나리오를 별도로 보고하지 않을까요?
XCTest는 XCTestCase 하위 클래스의 테스트 메서드를 검색합니다. 이러한 메서드는 매개변수를 받지 않으며 이름은 test로 시작합니다. 검색된 메서드 안의 루프는 해당 메서드의 일부로 남으므로, 반복 실행 항목이 별도로 검색되는 테스트가 되지는 않습니다. 어설션에 시나리오 이름을 추가하면 진단에는 도움이 되지만 개별 실행 제어 기능이 생기지는 않습니다. Apple의 테스트 케이스 및 테스트 메서드 정의를 참조하세요.
수명 주기도 같은 경계를 따릅니다. XCTest는 각 테스트 메서드 실행 전에 인스턴스 설정 메서드를 실행하고, 이후에 해제 메서드를 실행합니다. 따라서 5회 반복하는 루프가 자동으로 5번의 설정 및 해제 주기를 받지는 않습니다. 어떤 반복 실행 항목이 픽스처를 변경하면 테스트가 명시적으로 초기화하지 않는 한 다음 항목이 그 상태를 물려받을 수 있습니다. Apple은 테스트 상태 설정 및 해제에서 이 순서를 설명합니다.
Swift Testing은 여기서 필요한 결과를 제공합니다. 하나의 함수가 시나리오 인수를 받고, 실행기는 각 호출을 독립적인 케이스로 취급합니다. Xcode는 각 케이스의 인수를 표시하고 개별 실행 제어 기능을 제공합니다. Apple의 Swift Testing 더 알아보기에서 이 동작을 확인할 수 있습니다.
실패를 진단할 때 이 차이는 중요합니다. 어설션 실패와 조기 종료는 서로 다른 사건입니다. 일반적인 어설션이 발생해도 루프는 계속 검사할 수 있지만, 오류가 발생하거나 명시적으로 반환하면 이후 반복 실행 항목이 실행되지 않을 수 있습니다. 시나리오를 케이스로 분리하면 시나리오 간에 이런 제어 흐름을 공유하지 않게 됩니다. 프로세스를 종료시키는 실패를 복구할 수 있게 되는 것은 아닙니다.
기존 단위 테스트 대상 준비하기
이 작업 흐름에는 Xcode 16 이상을 사용하세요. Swift Testing은 해당 Xcode 배포판과 Swift 6.0부터의 Swift.org 툴체인에 포함되어 있습니다. Swift 프로젝트는 일반적인 테스트 작업에는 내장 사본을 사용할 것을 권장합니다. 이 예제에는 외부 패키지 의존성이 필요하지 않습니다. Swift Testing 받기를 참조하세요.
기존 단위 테스트 번들을 그대로 사용하세요. Apple은 같은 대상에서 Swift Testing과 XCTest를 함께 사용하는 방식을 공식 지원하므로, 새 Swift 파일 하나로 점진적인 마이그레이션을 시작할 수 있습니다. Xcode에서 File > New > New File From Template을 선택하고 Swift Testing Unit Test를 고른 다음, 파일을 기존 단위 테스트 대상에 추가하세요. 프로젝트에 테스트 대상이 없다면 File > New > Target을 통해 Unit Testing Bundle을 만드세요. 이 단계는 Xcode 프로젝트에 테스트 추가하기에 설명되어 있습니다.
이 안내에서는 계산 코드를 직접 테스트합니다. 데스크톱 윈도우를 자동화하지는 않습니다. UI 자동화에 대해 Apple이 문서화한 경로는 계속 XCTest를 사용합니다. 레이아웃을 결정하는 기반 로직에는 이 매개변수화 방식을 사용하세요. 같은 테스트 추가 안내에서 프레임워크의 적용 범위를 설명합니다.
각 시나리오를 완전한 인수 레코드로 만들기
Mac 앱이 타일을 행 단위로 배치한다고 가정해 보겠습니다. 열이 4개일 때 타일이 0개면 행은 0개, 타일이 4개면 한 행을 채우고, 타일이 5개면 두 행이 필요합니다. 구체적인 목표는 각 시나리오를 독립적으로 확인하고 실행할 수 있게 만들면서 이 검사들을 유지하는 것입니다.
시나리오 이름, 입력값, 예상 출력을 하나의 레코드에 담으세요. 케이스를 추가하거나 순서를 바꾸더라도 예상 결과가 해당 입력에 연결된 상태로 유지됩니다. 다음 다섯 레코드는 기존 애플리케이션의 관찰 결과가 아니라 예시용 테스트 데이터입니다.
| 시나리오 | 타일 수 | 열 수 | 예상 행 수 | 목적 |
|---|---|---|---|---|
empty |
0 | 4 | 0 | 입력이 비어 있음 |
one-tile |
1 | 4 | 1 | 첫 행의 일부만 채움 |
exact-row |
4 | 4 | 1 | 경계값과 정확히 일치 |
wrap |
5 | 4 | 2 | 경계를 넘는 첫 번째 타일 |
two-full-rows |
8 | 4 | 2 | 열 수의 정확한 배수 |
완전하고 자체적으로 실행 가능한 이 파일을 단위 테스트 대상에 추가하세요. 앱 모듈이나 다른 파일 없이 실행해 볼 수 있도록 합성 구현을 포함합니다. 서비스, 목, 파일 또는 네트워크에 의존하지 않습니다.
import Testing
private struct TileLayout {
func rowCount(tileCount: Int, columns: Int) -> Int {
precondition(tileCount >= 0)
precondition(columns > 0)
let fullRows = tileCount / columns
let partialRow = tileCount % columns == 0 ? 0 : 1
return fullRows + partialRow
}
}
private struct LayoutScenario:
Codable, Sendable, CustomTestStringConvertible {
let name: String
let tileCount: Int
let columns: Int
let expectedRows: Int
var testDescription: String { name }
static let examples: [LayoutScenario] = [
.init(name: "empty", tileCount: 0,
columns: 4, expectedRows: 0),
.init(name: "one-tile", tileCount: 1,
columns: 4, expectedRows: 1),
.init(name: "exact-row", tileCount: 4,
columns: 4, expectedRows: 1),
.init(name: "wrap", tileCount: 5,
columns: 4, expectedRows: 2),
.init(name: "two-full-rows", tileCount: 8,
columns: 4, expectedRows: 2)
]
}
struct TileLayoutTests {
@Test("Tile rows", arguments: LayoutScenario.examples)
fileprivate func rowCountMatchesScenario(
_ scenario: LayoutScenario
) {
let layout = TileLayout()
let actualRows = layout.rowCount(
tileCount: scenario.tileCount,
columns: scenario.columns
)
#expect(actualRows == scenario.expectedRows)
}
}
마이그레이션에서는 반복 실행을 @Test(arguments:)으로 옮깁니다. 각 요소가 한 번의 호출에 대한 인수가 됩니다. 선택적 재실행을 위해 Codable이 인코딩 지원을 제공하고, 읽기 쉬운 시나리오 레이블을 위해 CustomTestStringConvertible이 사용됩니다. Apple의 매개변수화된 테스트 구현하기에서 매개변수화를, Swift Testing 더 알아보기에서 사용자 지정 인수 설명을 확인할 수 있습니다.
픽스처는 의도적으로 테스트 함수 안에서 생성합니다. 각 호출은 자체 TileLayout을 만듭니다. 앞선 시나리오의 상태에 의존하는 것은 없습니다. 기존 픽스처를 마이그레이션한다면 설정을 의도적으로 옮기세요. XCTest의 기존 훅이 실행될 것이라고 기대해서는 안 됩니다. Swift Testing 스위트는 XCTestCase를 상속하지 않습니다. Apple은 XCTest에서 테스트 마이그레이션하기에서 설정은 이니셜라이저로, 클래스 또는 액터의 해제 작업은 deinit로 대응시킵니다.
이 계산은 0 이상의 타일 수와 0보다 큰 열 수를 받습니다. 전제 조건이 이 계약을 정의하며, 잘못된 값은 이 예제의 케이스에 포함되지 않습니다. 실제 애플리케이션 코드에서 잘못된 입력에 대해 복구 가능한 오류를 반환해야 한다면, 이 표를 확장하기 전에 API 계약을 변경하고 해당 테스트를 추가하세요.
별도 결과를 확인하고 실패한 케이스 하나를 재실행하기
선언 옆의 다이아몬드를 사용해 매개변수화된 함수를 실행하세요. 그런 다음 Test navigator를 열고 함수를 펼쳐 케이스를 확인합니다. 케이스 하나를 실행하려면 해당 케이스의 다이아몬드에 포인터를 가져간 다음 재생 컨트롤을 클릭하세요. 상위 함수의 재생 컨트롤을 클릭하면 모든 케이스가 실행됩니다. Apple의 테스트 실행 및 결과 해석에 두 동작이 설명되어 있습니다.
보고 동작을 직접 확인하려면 wrap 레코드의 expectedRows을 2에서 3로 임시 변경하세요. 매개변수화된 함수 전체를 실행합니다. 예상 결과는 5개 케이스가 실행되고 wrap 비교가 실패하는 것입니다. 구현이 2을 반환하기 때문입니다. 해당 케이스를 선택해 개별적으로 재실행한 다음, 예상값을 2로 복원하세요. 이는 의도적인 테스트 데이터 변경이지, 구현 결함의 증거가 아닙니다.
실패한 기대값의 Results 및 Arguments 세부 정보를 확인해 계산된 값과 시나리오 입력을 비교하세요. 전체 실행 결과를 보려면 View > Navigators > Reports를 열고 테스트 작업을 선택한 다음 Tests 섹션을 확인합니다. 이 확인 방법은 Apple의 테스트 결과 안내에 설명되어 있습니다.
선택적 실행에는 읽기 쉬운 레이블 외에도 추가 요구 사항이 있습니다. 실행기가 모든 인수를 결정적으로 일치시킬 수 있어야 합니다. Apple의 최신 문서는 CustomTestArgumentEncodable, 특정 RawRepresentable 타입, Encodable, 또는 인코딩 가능한 식별자가 있는 Identifiable을 허용합니다. 이 예제의 Codable 레코드는 인코딩 방식을 충족합니다. 사용자 지정 표시 문자열만으로는 이 요구 사항을 충족하지 못합니다. 매개변수화된 테스트 구현하기를 참조하세요.
다섯 레코드 모두의 예상 산술 결과는 Python의 정수 나눗셈과 나머지 연산으로 로컬에서 확인했습니다. 정수 나눗셈만 사용하는 잘못된 예시 구현은 one-tile와 wrap에서 실패합니다. 이 조사 환경에서는 Swift를 사용할 수 없어 Swift 파일을 검사하기만 하고 컴파일하지 않았습니다. 위의 Xcode 실행이 실제 검증 단계로 남아 있습니다.
의도한 케이스 수와 수명 주기 유지하기
유용한 마이그레이션 확인 방법은 세 가지를 비교하는 것입니다. 시나리오 레코드 수, 전체 실행에서 수행된 케이스 수, 선택한 케이스를 단독으로 실행할 수 있는지 여부입니다. 이 예제에서 의도한 수는 5입니다. 수가 다르면 계산을 변경하기 전에 인수 생성과 테스트 선택을 살펴보세요.
서로 짝을 이루는 기대값은 함께 유지하세요. arguments:에 별도의 컬렉션 두 개를 전달하면 두 컬렉션의 데카르트 곱이 생성됩니다. 입력값 5개와 예상 출력 5개를 넣으면 짝이 맞는 시나리오 5개가 아니라 조합 25개가 됩니다. 모든 조합에 의미가 있다면 별도 컬렉션을 사용하고, 짝을 이룬 데이터에는 레코드 또는 zip()를 사용하세요. Apple의 매개변수화된 테스트 구현하기에서 차이를 설명합니다.
zip()을 사용하는 경우 쌍을 만들기 전에 컬렉션 길이를 확인하세요. Swift는 더 짧은 시퀀스의 길이에서 멈춥니다. 그러면 케이스를 생성하지 않은 채 추가 예상 결과가 사용되지 않을 수 있습니다. 시나리오 레코드 하나를 사용하면 이런 불일치를 피할 수 있습니다. 이 잘라내기 동작은 Swift의 zip(::) 참조 문서에 설명되어 있습니다.
실행 순서에 따라 마이그레이션 방식도 달라집니다. Swift Testing은 기본적으로 케이스를 병렬로 실행합니다. 이 예제는 인수가 불변이고 픽스처가 로컬이므로 결과가 순서에 좌우되지 않습니다. 매개변수화된 함수에 일시적으로 순차 실행이 필요한 경우 @Test 속성에 .serialized를 추가하세요. 이 특성을 스위트에 적용하면 그 안의 테스트와 케이스도 순차 실행되지만, 관련 없는 다른 스위트까지 순차 실행되지는 않습니다. 테스트를 순차 또는 병렬로 실행하기를 참조하세요.
순차 실행이 새 상태를 만들거나 시나리오 간 의존성 계약을 세워 주지는 않습니다. 한 케이스가 다른 케이스의 상태 변경 이후에만 성공한다면, 단독으로 실행할 수 있도록 구성을 다시 설계하세요. 단계들이 의도적으로 하나의 연속된 작업을 구성한다면, 이를 하나의 테스트 안에 유지하고 완전한 작업 단위에 매개변수화를 적용하세요.
이 마이그레이션 예제에서는 Swift Testing 어설션만 사용하세요. XCTest 어설션이 포함된 헬퍼를 재사용할 경우 툴체인의 상호 운용 동작을 확인하세요. Apple의 최신 마이그레이션 안내에서는 툴체인과 패키지 도구 버전에 따라 달라지는 모드와 기본값을 설명합니다. 이 파일 전체를 Testing으로 구성하면 그 의존성을 피할 수 있습니다. XCTest에서 테스트 마이그레이션하기를 참조하세요.
마이그레이션을 미뤄야 한다면 지원되는 XCTest 대안은 시나리오마다 이름이 지정된 매개변수 없는 테스트 메서드를 하나씩 만들고, 필요에 따라 계산 및 어설션 헬퍼를 공유하는 것입니다. Apple은 테스트 추가 안내에서 시나리오마다 테스트를 하나씩 둘 것을 권장합니다. Swift Testing의 매개변수화를 사용하지 않고도 메서드별 개별 결과를 유지할 수 있습니다.
각 기존 시나리오에 대응하는 실행된 케이스가 있고, 픽스처가 의도한 상태에서 시작하며, 선택한 케이스를 단독으로 성공적으로 실행할 수 있다면 변환이 완료된 것입니다. 변경하는 동안 경계 입력과 예상 출력을 유지하세요. 개별 보고 기능을 활용하면 기존 검사를 더 쉽게 찾고, 진단하고, 반복할 수 있습니다.
출처
- 테스트 케이스 및 테스트 메서드 정의 — Apple
- 테스트 상태 설정 및 해제 — Apple
- Swift Testing 더 알아보기 — Apple
- Swift Testing 받기 — Swift 프로젝트
- Xcode 프로젝트에 테스트 추가하기 — Apple
- 매개변수화된 테스트 구현하기 — Apple
- XCTest에서 테스트 마이그레이션하기 — Apple
- 테스트 실행 및 결과 해석 — Apple
- zip(::) — Apple
- 테스트를 순차 또는 병렬로 실행하기 — Apple