Slide 1

Slide 1 text

Swiftの “private” を テストする id:yutailang0119 / @yutailang0119 Mobile Act OSAKA 15 1

Slide 2

Slide 2 text

Who am I!? ● id:yutailang0119 ○ @yutailang0119 ● 株式会社はてな ○ @京都オフィス ○ アプリケーションエンジニア ■ iOS/Android ○ サービスプラットフォームチーム ● try! Swift Tokyo Organizer ● AVP座談会 🥽 2

Slide 3

Slide 3 text

3 Introduction

Slide 4

Slide 4 text

Introduction ● private/fileprivateは、参照範囲を制限 ○ @testableもアクセスできない ○ open, package, internalはアクセス可能 ● Javaには @VisibleForTesting がある ○ https://developer.android.com/reference/androidx/annotation/VisibleForTesting 4

Slide 5

Slide 5 text

Access Level 5 ● open ● package ● internal ● fileprivate ● private https://docs.swift.org/swift-book/documentation/the-swift-programming-language/accesscontrol/

Slide 6

Slide 6 text

6 Motivation

Slide 7

Slide 7 text

Motivation ● テストのためのinternalを避けたい ○ e.g.) 🙅 コメントでの規制 VisibleForTesting ● アクセスのバイパス手段がほしい 7

Slide 8

Slide 8 text

8 Proposed solution

Slide 9

Slide 9 text

9 yutailang0119/swift-bypass-accessing

Slide 10

Slide 10 text

yutailang0119/swift-bypass-accessing ● Swift Macros ○ @BypassAccess ● ___ prefix付きコード生成 PeerMacro ● #if DEBUG & internal 10

Slide 11

Slide 11 text

PeerMacro @attached(peer, names: prefixed(___), named(___init)) public macro BypassAccess() = #externalMacro( module: "BypassAccessingMacros", type: "BypassAccessMacro" ) 11

Slide 12

Slide 12 text

12 Detailed design

Slide 13

Slide 13 text

Detailed design import BypassAccessing struct Declaration { @BypassAccess private let property: String = "property" @BypassAccess fileprivate func function() -> String { #function } } 13

Slide 14

Slide 14 text

Detailed design import Testing import BypassAccessing @testable import Example struct Tests { @Test func test() { let declaration = Declaration() #if DEBUG #expect(declaration.___property == "property") #expect(declaration.___function() == "function()") #endif } } 14

Slide 15

Slide 15 text

15 Macro

Slide 16

Slide 16 text

Macro import BypassAccessing struct Declaration { @BypassAccess private let property: String = "property" } 16

Slide 17

Slide 17 text

Expand Macro import BypassAccessing struct Declaration { @BypassAccess private let property: String = "property" #if DEBUG var ___property: String { property } #endif } 17

Slide 18

Slide 18 text

Macro import BypassAccessing struct Declaration { @BypassAccess fileprivate func function() -> String { #function } } 18

Slide 19

Slide 19 text

Expand Macro import BypassAccessing struct Declaration { @BypassAccess fileprivate func function() -> String { #function } #if DEBUG func ___function() -> String { function() } #endif } 19

Slide 20

Slide 20 text

20 Support Grammar

Slide 21

Slide 21 text

Support Grammar ● let ● var ○ Stored ○ Computed ● func ● init 21

Slide 22

Slide 22 text

Modifier, Accessor ● async, @MainActor ● throws ● static, class ● inout ● Generics 22 https://docs.swift.org/swift-book/documentation/the-swift-programming-language/summaryofthegrammar

Slide 23

Slide 23 text

Modifier, Accessor 23 import BypassAccessing struct Declaration { @BypassAccess @MainActor private init(_ arg: inout String) async throws where Element: Equatable {} #if DEBUG @MainActor static func ___init(_ arg: inout String) async throws -> Self where Element: Equatable { try await Self.init(_: &arg) } #endif }

Slide 24

Slide 24 text

Limitation ● ❌ privateが現れるインターフェース ○ Method must be declared private because its parameter uses a private type ● ❌ 型推論を伴うプロパティ ○   ○ '@BypassAccess' attribute require TypeAnnotation 24 @BypassAccess private var value = "value"

Slide 25

Slide 25 text

Support Grammar ● 想定利用ケースは、テストを参照 ○ https://github.com/yutailang0119/swift-bypass-accessing/tree/main/Tests/BypassAccessingTests ● 考慮外パターンが存在しそう🫥 25

Slide 26

Slide 26 text

小噺 ● SwiftSyntaxBuilder ○ 複雑だが、今回のケースでは便利 ■ 条件に応じて、Expressionを組み合わせ可能 ○ PlainTextで実装後、SyntaxBuilderで書き直した ● Macroのテストは swift-testing が使えない ○ XCTest ○ https://github.com/swiftlang/swift-syntax/issues/2720 26

Slide 27

Slide 27 text

27 Conclusion

Slide 28

Slide 28 text

Conclusion ● yutailang0119/swift-bypass-accessing ○ @BypassAccess ● 想定外を見つけたら、ご連絡ください📲 28

Slide 29

Slide 29 text

29 Advertisement

Slide 30

Slide 30 text

30

Slide 31

Slide 31 text

try! Swift Tokyo 2025 ● 2025/04/09 - 11 ● 立川ステージガーデン ● https://tryswift.jp/ 31

Slide 32

Slide 32 text

hatena.co.jp/recruit 32 32