|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "[SwiftTesting] Swift Testing 분석 (1) - Expand Macro(Test)" |
| 4 | +tags: [swift, testing, Swift Testing] |
| 5 | +--- |
| 6 | +{% include JB/setup %} |
| 7 | + |
| 8 | +Xcode 16이 출시되면서 새로운 테스트 패키지인 [Swift Testing](https://github.com/swiftlang/swift-testing)이 추가되었습니다. 기존 XCTest를 이용하여 테스트를 작성했다면, 이제는 Swift Testing을 활용하여 현대적인 테스트 케이스를 작성할 수 있게 되었습니다. |
| 9 | + |
| 10 | +```swift |
| 11 | +import Testing |
| 12 | + |
| 13 | +struct SampleTest { |
| 14 | + init() {} |
| 15 | + |
| 16 | + @Test("Hello 테스트") |
| 17 | + func hello() { |
| 18 | + #expect(true) |
| 19 | + } |
| 20 | + |
| 21 | + @Test("World 테스트") |
| 22 | + func world() { |
| 23 | + #expect(false != true) |
| 24 | + } |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +XCTest를 사용할 때는 함수명이 곧 테스트 케이스 이름이었지만, 이제는 Test 매크로에 표시할 이름을 넣을 수 있게 되었습니다. |
| 29 | + |
| 30 | +현대적인 방식으로 테스트 케이스를 작성할 수 있게 되었지만, Xcode는 어떻게 `@Test` 매크로가 붙어 있는 함수를 찾아서 수행하는 것일까요? |
| 31 | + |
| 32 | +`@Test`에서 Expand Macro를 실행하여 어떻게 코드가 생성되는지 확인할 수 있습니다. |
| 33 | + |
| 34 | +<br/> |
| 35 | +<p style="text-align:center;"> |
| 36 | +<img src="{{ site.prod_url }}/image/2024/12/01.png"/> |
| 37 | +</p><br/> |
| 38 | + |
| 39 | +```swift |
| 40 | +@available(*, deprecated, message: "This function is an implementation detail of the testing library. Do not use it directly.") |
| 41 | +@Sendable private static func $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_() async throws -> Void { |
| 42 | + try await Testing.__ifMainActorIsolationEnforced { [] in |
| 43 | + let $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_ = try await (SampleTest(), Testing.__requiringTry, Testing.__requiringAwait).0 |
| 44 | + _ = try await ($s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_.hello(), Testing.__requiringTry, Testing.__requiringAwait).0 |
| 45 | + } else: { [] in |
| 46 | + let $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_ = try await (SampleTest(), Testing.__requiringTry, Testing.__requiringAwait).0 |
| 47 | + _ = try await ($s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu_.hello(), Testing.__requiringTry, Testing.__requiringAwait).0 |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +@available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.") |
| 52 | +enum $s18SampleLibraryTests0A4TestV5hello0D0fMp_41__🟠$test_container__function__funchello__fMu_: Testing.__TestContainer { |
| 53 | + static var __tests: [Testing.Test] { |
| 54 | + get async { |
| 55 | + return [ |
| 56 | + .__function( |
| 57 | + named: "hello()", |
| 58 | + in: SampleTest.self, |
| 59 | + xcTestCompatibleSelector: nil, |
| 60 | + displayName: "Hello 테스트", traits: [], sourceLocation: Testing.SourceLocation(fileID: "SampleLibraryTests/SampleLibraryTests.swift", filePath: "/Users/minsone/tmp/20241216/SampleLibrary/Tests/SampleLibraryTests/SampleLibraryTests.swift", line: 9, column: 6), |
| 61 | + parameters: [], |
| 62 | + testFunction: $s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_ |
| 63 | + ) |
| 64 | + ] |
| 65 | + } |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +`$s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_` 함수와 `$s18SampleLibraryTests0A4TestV5hello0D0fMp_41__🟠$test_container__function__funchello__fMu_` enum이 만들어졌습니다.<br/> |
| 71 | + |
| 72 | +여기에서 우리는 함수 이름이 Mangling 되어 있다는 것을 알 수 있습니다. [Wikipedia - Name mangling](https://en.wikipedia.org/wiki/Name_mangling#Swift), [Swift - Mangling](https://github.com/swiftlang/swift/blob/main/docs/ABI/Mangling.rst), [Name Mangling](https://minsone.github.io/programming/swift-name-mangling) |
| 73 | + |
| 74 | +이 함수 이름을 Demangle 해봅시다. |
| 75 | + |
| 76 | +```shell |
| 77 | +$ xcrun swift-demangle s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_ |
| 78 | +$s18SampleLibraryTests0A4TestV5hello0D0fMp_11funchello__fMu0_ ---> unique name #2 of funchello__ in peer macro @Test expansion #1 of hello in SampleLibraryTests.SampleTest |
| 79 | +``` |
| 80 | + |
| 81 | +`s` 는 Swift 심볼을 의미, `18SampleLibraryTests` 는 `SampleLibraryTests` 모듈 이름 및 모듈 이름 글자수인 18자, `0A4TestV`는 Test 라는 Value 타입인 구조체, `5hello0D0`는 메서드나 속성 이름을 나타냅니다. |
| 82 | + |
| 83 | +<br/> |
| 84 | + |
| 85 | +다음으로 enum 코드를 살펴보면, 특이하게 `🟠` 이모지가 들어있는 것을 확인할 수 있습니다. 왜 이런 이모지가 들어있는 것일까요? 알아보기 위해 [Swift Testing](https://github.com/swiftlang/swift-testing) 라이브러리를 살펴봅시다. |
| 86 | + |
| 87 | +<br/> |
| 88 | +<p style="text-align:center;"> |
| 89 | +<img src="{{ site.prod_url }}/image/2024/12/02.png"/> |
| 90 | +</p> |
| 91 | + |
| 92 | +[GitHub 검색 결과](https://github.com/search?q=repo%3Aswiftlang%2Fswift-testing%20%F0%9F%9F%A0&type=code) |
| 93 | + |
| 94 | +<br/> |
| 95 | + |
| 96 | +검색을 통해 [TestDeclarationMacro](https://github.com/swiftlang/swift-testing/blob/e2ec0411e5f7407fc2d325c9feea8f0ac10a60e2/Sources/TestingMacros/TestDeclarationMacro.swift#L467) 매크로가 `__🟠$test_container__function__` 문자열을 붙여준다는 것을 확인할 수 있으며, [Test+Discovery.swift](https://github.com/swiftlang/swift-testing/blob/e2ec0411e5f7407fc2d325c9feea8f0ac10a60e2/Sources/Testing/Test%2BDiscovery.swift#L26) 에서 `__🟠$test_container__` 문자열로 무엇인가 발견하려는 것을 알 수 있습니다. |
| 97 | + |
| 98 | +<br/> |
| 99 | + |
| 100 | +다음 편에서 `Test+Discovery.swift` 코드부터 살펴보면서 어떻게 테스트 케이스를 찾아서 실행하는지 살펴보겠습니다. |
| 101 | + |
| 102 | +<br/> |
| 103 | + |
| 104 | +## 참고자료 |
| 105 | + |
| 106 | +* [Swift Testing](https://github.com/swiftlang/swift-testing) |
| 107 | +* [Displaying all SwiftUI Previews in a Storybook app](https://medium.com/eureka-engineering/displaying-all-swiftui-previews-in-a-storybook-app-1dd8e925d777) |
| 108 | + * [eure/Storybook-ios](https://github.com/eure/Storybook-ios) |
0 commit comments