Slide 1

Slide 1 text

マルチモジュールにおける テスト最適化 Fumitaka Watanabe / id:fxwx23 2024/09/02 iOSDC 2024 後夜祭

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

自己紹介 ● Fumitaka Watanabe ○ @fxwx23 ● 株式会社はてな ○ マンガアプリチーム ○ GigaViewer for Apps [1] [1] https://hatena.co.jp/solutions/gigaviewer

Slide 4

Slide 4 text

前置き CIの高速化とコスト削減

Slide 5

Slide 5 text

CIの高速化とコスト削減 ● インスタンスタイプ見直し ● キャッシュ活用 ● テスト実行の並列化 ● 不必要にテストを実行しない ● Flakyなテストを直す etc

Slide 6

Slide 6 text

CIの高速化とコスト削減 ● インスタンスタイプ見直し ● キャッシュ活用 ● テスト実行の並列化 ● 不必要にテストを実行しない ● Flakyなテストを直す etc

Slide 7

Slide 7 text

不必要なテストの実行 ● commit度にCIでフルテストが実行される

Slide 8

Slide 8 text

不必要なテストの実行 ● commit度にCIでフルテストが実行される ○ → 変更とは関係ないテストの実行

Slide 9

Slide 9 text

不必要なテストの実行 ● commit度にCIでフルテストが実行される ○ → 変更とは関係ないテストの実行 ● Flakyなテストがある場合、変更とは関係ない テストが落ちる

Slide 10

Slide 10 text

不必要なテストの実行 ● commit度にCIでフルテストが実行される ○ → 変更とは関係ないテストの実行 ● Flakyなテストがある場合、変更とは関係ない テストが落ちる ○ → テストが落ちるとJobを再実行することになる ○ → CIの時間が開発のボトルネックに

Slide 11

Slide 11 text

変更に関係するテスト のみ実行したい

Slide 12

Slide 12 text

変更に関係するテスト ● 変更差分がどのモジュールに影響しているか

Slide 13

Slide 13 text

変更に関係するテスト ● 変更差分がどのモジュールに影響しているか

Slide 14

Slide 14 text

変更に関係するテスト ● 変更差分がどのモジュールに影響しているか ○ → マルチモジュールで適切に依存関係が整理されてい ることが前提

Slide 15

Slide 15 text

マルチモジュール ● 機能単位やレイヤー単位でモジュールを分割 ● 嬉しいこと ○ 機能同士の依存関係の整理 ○ ビルド時間の削減 ○ 複数人での並行開発時のコンフリクト回避 ○ 機能の取捨選択 etc

Slide 16

Slide 16 text

マルチモジュール ● 機能単位やレイヤー単位でモジュールを分割 ● 嬉しいこと ○ 機能同士の依存関係の整理 ○ ビルド時間の削減 ○ 複数人での並行開発時のコンフリクト回避 ○ 機能の取捨選択 etc

Slide 17

Slide 17 text

📱App Feature 📦 Feature 📦 Feature 📦 Feature 📦 Common 📦 CommonUI 📦 Model 📦 Protocol

Slide 18

Slide 18 text

📱App Feature 📦 Feature 📦 Feature 📦 Feature 📦 Common 📦 CommonUI 📦 Model 📦 Protocol

Slide 19

Slide 19 text

📱App Feature 📦 Feature 📦 Feature 📦 Feature 📦 Common 📦 CommonUI 📦 Model 📦 Protocol

Slide 20

Slide 20 text

どのモジュールに影響するか ● Xcodeプロジェクト内のターゲット間の依存 ● ターゲットとSwift Package間の依存 ● Swift Package内でのターゲット間の依存

Slide 21

Slide 21 text

Xcodeプロジェクト内のターゲット間 の依存関係

Slide 22

Slide 22 text

Xcodeプロジェクト内のターゲット間 の依存関係 ● App.xcworkspace > App.xcproj > project.pbxproj > PBXTarget > PBXNativeTarget > dependencies http://www.monobjc.net/xcode-project-file-format.html

Slide 23

Slide 23 text

● App.xcworkspace > App.xcproj > project.pbxproj > PBXTarget > PBXNativeTarget > dependencies 491FBA866697560BBEF6A475 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3478A70B9E3147C13F640454 /* AModule */; }; 4C4FDDD28591957330786D9B /* App */ = { isa = PBXNativeTarget; dependencies = ( 491FBA866697560BBEF6A475 /* PBXTargetDependency */ ); } http://www.monobjc.net/xcode-project-file-format.html Xcodeプロジェクト内のターゲット間 の依存関係

Slide 24

Slide 24 text

● App.xcworkspace > App.xcproj > project.pbxproj > PBXTarget > PBXNativeTarget > dependencies 491FBA866697560BBEF6A475 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3478A70B9E3147C13F640454 /* AModule */; }; 4C4FDDD28591957330786D9B /* App */ = { isa = PBXNativeTarget; dependencies = ( 491FBA866697560BBEF6A475 /* PBXTargetDependency */ ); } http://www.monobjc.net/xcode-project-file-format.html Xcodeプロジェクト内のターゲット間 の依存関係

Slide 25

Slide 25 text

ターゲットとSwift Package間の依存

Slide 26

Slide 26 text

ターゲットとSwift Package間の依存 4C4FDDD28591957330786D9B /* AModule */ = { isa = PBXNativeTarget; packageProductDependencies = ( 1885F5AAE69D8D13E59C4ECF /* BModule */, ); }; http://www.monobjc.net/xcode-project-file-format.html ● App.xcworkspace > App.xcproj > project.pbxproj > PBXTarget > PBXNativeTarget > packageProductDependencies

Slide 27

Slide 27 text

Swift Package 内でのターゲット間の 依存

Slide 28

Slide 28 text

Swift Package 内でのターゲット間の 依存 ● $ swift package dump-package { "targets": [ { "dependencies": [ { "byName": ["CModule", null] } ], "name": "BModule" } ] }

Slide 29

Slide 29 text

XcodeSelectiveTestin g https://github.com/mikeger/XcodeSelectiveTesting

Slide 30

Slide 30 text

XcodeSelectiveTesting ● $ swift run xcode-selective-testing --test-plan App.xctestplan ● $ swift run xcode-selective-testing --json https://github.com/mikeger/XcodeSelectiveTesting

Slide 31

Slide 31 text

JSON Output [ { "name": "AppTests", "type": "target", "path": "\/path\/to\/your\/App.xcodeproj", "testTarget": true }, { "name": "App", "type": "target", "path": "\/path\/to\/your\/App.xcodeproj", "testTarget": false } ]

Slide 32

Slide 32 text

JSON Output [ { "name": "AppTests", "type": "target", "path": "\/path\/to\/your\/App.xcodeproj", "testTarget": true }, { "name": "App", "type": "target", "path": "\/path\/to\/your\/App.xcodeproj", "testTarget": false } ] $ jq -r "[.[] | select(.testTarget == true)] | map(.name) | join(\",\")"

Slide 33

Slide 33 text

JSON Output [ { "name": "AppTests", "type": "target", "path": "\/path\/to\/your\/App.xcodeproj", "testTarget": true }, { "name": "App", "type": "target", "path": "\/path\/to\/your\/App.xcodeproj", "testTarget": false } ] $ jq -r "[.[] | select(.testTarget == true)] | map(.name) | join(\",\")" AppTests

Slide 34

Slide 34 text

fastlane lane :selective_test do |options| if options[:targets].nil? then UI.user_error!("The required parameter is invalid or missing.") end if options[:targets].empty? then UI.user_error!("At least one target is required.") end run_tests( scheme: "...", only_testing: options[:targets], ) end

Slide 35

Slide 35 text

fastlane TARGETS=$(./scripts/affected-targets.sh) if [ -n "$TARGETS" ]; then bundle exec fastlane selective_test targets:"$TARGETS" else bundle exec fastlane test fi https://fxwx23.hatenablog.com/entry/tips-integrating-xcode-selective-testing

Slide 36

Slide 36 text

フルテスト +-------------------------------+ | Test Results | +-------------------------+-----+ | Number of tests | 674 | | Number of failures | 0 | +-------------------------+-----+

Slide 37

Slide 37 text

フルテスト +-------------------------------+ | Test Results | +-------------------------+-----+ | Number of tests | 15 | | Number of failures | 0 | +-------------------------+-----+ +-------------------------------+ | Test Results | +-------------------------+-----+ | Number of tests | 674 | | Number of failures | 0 | +-------------------------+-----+ Application Target 変更の み

Slide 38

Slide 38 text

💪 フルテスト +-------------------------------+ | Test Results | +-------------------------+-----+ | Number of tests | 15 | | Number of failures | 0 | +-------------------------+-----+ +-------------------------------+ | Test Results | +-------------------------+-----+ | Number of tests | 674 | | Number of failures | 0 | +-------------------------+-----+ Application Target 変更の み

Slide 39

Slide 39 text

運用方針 ● main、 Package更新のPRはフルテスト ○ 任意のPRでフルテストに切り替える等は今後の課題 ● 変更差分がどのモジュールにも影響していな い場合はフルテスト ○ テストが実行されていないことに気づけないリスクは 防ぎたい

Slide 40

Slide 40 text

導入してみて ● 影響範囲が小さいほど恩恵が大きい ● Flakyなテストの回避 ○ Success Rate 向上の貢献 ● CPU利用や消費クレジットへの貢献

Slide 41

Slide 41 text

まとめ ● マルチモジュールはテスト実行の最適化に活 用できる ● テスト実行の最適化はCIの高速化とコスト削 減に貢献できる

Slide 42

Slide 42 text

地球環境に優しい CI実行を目指していきましょう 🌏

Slide 43

Slide 43 text

Thank you!

Slide 44

Slide 44 text

hatena.co.jp/recruit