[SwiftTesting] Swift Testing 분석 (1) - Expand Macro(Test)
Xcode 16이 출시되면서 새로운 테스트 패키지인 Swift Testing이 추가되었습니다. 기존 XCTest를 이용하여 테스트를 작성했다면, 이제는 Swift Testing을 활용하여 현대적인 테스트 케이스를 작성할 수 있게 되었습니다.
import Testing
struct SampleTest {
init() {}
@Test("Hello 테스트")
func hello() {
#expect(true)
}
@Test("World 테스트")
func world() {
#expect(false != true)
}
}
XCTest를 사용할 때는 함수명이 곧 테스트 케이스 이름이었지만, 이제는 Test 매크로에 표시할 이름을 넣을 수 있게 되었습니다.
현대적인 방식으로 테스트 케이스를 작성할 수 있게 되었지만, Xcode는 어떻게 @Test
매크로가 붙어 있는 함수를 찾아서 수행하는 것일까요?
@Test
에서 Expand Macro를 실행하여 어떻게 코드가 생성되는지 확인할 수 있습니다.
@available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.")
@Sendable private static func $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_() async throws -> Void {
try await Testing.__ifMainActorIsolationEnforced { [] in
let $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_ = try await (SampleTest(), Testing.__requiringTry, Testing.__requiringAwait).0
_ = try await ($s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_.hello(), Testing.__requiringTry, Testing.__requiringAwait).0
} else: { [] in
let $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_ = try await (SampleTest(), Testing.__requiringTry, Testing.__requiringAwait).0
_ = try await ($s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_.hello(), Testing.__requiringTry, Testing.__requiringAwait).0
}
}
@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
enum $s18SampleLibraryTests0A4TestV5hello0D0fMp_41__🟠$test_container__function__funchello__fMu_: Testing.__TestContainer {
static var __tests: [Testing.Test] {
get async {
return [
.__function(
named: "hello()",
in: SampleTest.self,
xcTestCompatibleSelector: nil,
displayName: "Hello 테스트", traits: [], sourceLocation: Testing.SourceLocation(fileID: "SampleLibraryTests/SampleLibraryTests.swift", filePath: "/Users/minsone/tmp/20241216/SampleLibrary/Tests/SampleLibraryTests/SampleLibraryTests.swift", line: 9, column: 6),
parameters: [],
testFunction: $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_
)
]
}
}
}
$s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_
함수와 $s18SampleLibraryTests0A4TestV5hello0D0fMp_41__🟠$test_container__function__funchello__fMu_
enum이 만들어졌습니다.
여기에서 우리는 함수 이름이 Mangling 되어 있다는 것을 알 수 있습니다. Wikipedia - Name mangling, Swift - Mangling, Name Mangling
이 함수 이름을 Demangle 해봅시다.
$ xcrun swift-demangle s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_
$s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_ ---> unique name #2 of funchello__ in peer macro @Test expansion #1 of hello in SampleLibraryTests.SampleTest
s
는 Swift 심볼을 의미, 18SampleLibraryTests
는 SampleLibraryTests
모듈 이름 및 모듈 이름 글자수인 18자, 0A4TestV
는 Test 라는 Value 타입인 구조체, 5hello0D0
는 메서드나 속성 이름을 나타냅니다.
다음으로 enum 코드를 살펴보면, 특이하게 🟠
이모지가 들어있는 것을 확인할 수 있습니다. 왜 이런 이모지가 들어있는 것일까요? 알아보기 위해 Swift Testing 라이브러리를 살펴봅시다.
검색을 통해 TestDeclarationMacro 매크로가 __🟠$test_container__function__
문자열을 붙여준다는 것을 확인할 수 있으며, Test+Discovery.swift 에서 __🟠$test_container__
문자열로 무엇인가 발견하려는 것을 알 수 있습니다.
다음 편에서 Test+Discovery.swift
코드부터 살펴보면서 어떻게 테스트 케이스를 찾아서 실행하는지 살펴보겠습니다.