Slide 1

Slide 1 text

© GO Inc. iOSアプリ開発における効果的 なUnitTestの戦略とその実装 2023.09.26 開発本部 ソフトウェア開発統括部 ユーザーシステム開発部 ユーザーシステム1グループ / 髙橋秀宗 GO株式会社

Slide 2

Slide 2 text

© GO Inc. 2 自己紹介 GO株式会社 ユーザーシステム1グループ / 髙橋秀宗 2022年12月に入社。タクシーアプリ『GO』のiOSアプリ 開発を担当 趣味はエレキギター演奏 お気に入りのアンプはMarshall JCM800 @h1d3mun3

Slide 3

Slide 3 text

Index © GO Inc. 1. UnitTestとは 2. 単一責任原則 3. 実践UnitTest 4. まとめ 5. 参考資料 3

Slide 4

Slide 4 text

© GO Inc. UnitTestとは 01

Slide 5

Slide 5 text

© GO Inc. V字モデル 5 実装 基本設計 結合テスト 詳細設計 コンポーネントテスト (単体テスト ・UnitTest) 要件定義 システム/受け入れ テスト 対応するテストスコープ https://shiftasia.com/ja/column/v%E5%AD%97%E3%83%A2%E3%83%87%E3%83%AB%E3%81%A8%E3%81%AF/

Slide 6

Slide 6 text

© GO Inc. V字モデル 6 実装 基本設計 結合テスト 詳細設計 コンポーネントテスト (単体テスト ・UnitTest) 要件定義 システム/受け入れ テスト 対応するテストスコープ https://shiftasia.com/ja/column/v%E5%AD%97%E3%83%A2%E3%83%87%E3%83%AB%E3%81%A8%E3%81%AF/

Slide 7

Slide 7 text

© GO Inc. ● 詳細設計で実装したものをテストする ○ 作ったクラスや、変更した処理など ● 要件定義・基本設計レベルの観点はテストしない UnitTestのスコープ 7

Slide 8

Slide 8 text

© GO Inc. ● ホワイトボックステスト ○ システムの内部も考慮したテスト ○ if式やswitch式、guard式などが動くかどうかも考慮する ● ブラックボックステスト ○ システムの外部をテストする ○ 入力に対して期待する出力かどうかを考慮する ホワイトボックステスト・ブラックボックステスト 8

Slide 9

Slide 9 text

© GO Inc. ● 🙆 メリット 🙆 ○ 高速に、何度も、正確に実施できる ○ 実施コストが低い ● 🙅 デメリット🙅 ○ 状況に応じて操作を分けるのが難しい ○ 人間的な操作など、非規則的なテストが難しい UnitTestの特徴 9

Slide 10

Slide 10 text

© GO Inc. ● テスト対象 ○ 詳細設計に対応するので、実装したクラスや処理などをチェック ● コスト ○ 実行コストが低いので、CI上でDailyで回すなどして、問題の早期発見に活 用する ● テスト方式 ○ 問題の早期発見を主眼にするので、クラスや処理の入力と出力を見るブラッ クボックステストを行う UnitTestの効果的な使い方 10

Slide 11

Slide 11 text

© GO Inc. 単一責任原則 02

Slide 12

Slide 12 text

© GO Inc. ● モジュールはたったひとつのアクターに対して責務を負うべきである、という原 則 ● アクターとは「ユーザーやステークホルダーのグループ」のこと ○ もちろんソースコード内部の別ファイルもアクターになりうる 🎉 → これが守られていると、モジュール(≒ソースコード)を変更する理由は必ず1つに なる 単一責任原則 12

Slide 13

Slide 13 text

© GO Inc. 実践UnitTest 03

Slide 14

Slide 14 text

© GO Inc. 1. 既存のコードをアクターを単位にして分割する 2. 分割したコードの振る舞いをProtocolに抽象化しDI可能にする 3. それぞれに対してMockを利用してテストをする 既存のコードにUnitTestを適用させる 14

Slide 15

Slide 15 text

© GO Inc. やってみよう! ● Notification構造体でアプリ内お知らせを表現 ● NotificationManagerクラスが、アプリ内お知らせを管理 し、下記を行っている ○ 現在のお知らせ一覧を返却する機能 ○ メッセージの既読処理 ○ APIを経由したサーバーへの反映処理 15

Slide 16

Slide 16 text

© GO Inc. Notification構造体 struct Notification { let title: String let isRead: Bool } 16

Slide 17

Slide 17 text

© GO Inc. NotificationManagerクラス class NotificationManager { func getAllUnReadNotifications() -> [Notification] { 〜〜〜 } func markAsRead(notification: Notification, completion: @escaping (() -> Void)) { 〜〜〜 } func postApi(notification: Notification, completion: @escaping (() -> Void)) { 〜〜〜 } } 17

Slide 18

Slide 18 text

© GO Inc. 修正前クラス図 18

Slide 19

Slide 19 text

© GO Inc. 1. 既存のコードをアクターを単位にして分割する 2. 分割したコードの振る舞いをProtocolに抽象化しDI可能にする 3. それぞれに対してMockを利用してテストをする 再掲: 既存のコードにUnitTestを適用させる 19

Slide 20

Slide 20 text

© GO Inc. 各責務をそれぞれ別々のファイルに分割する ● メッセージの既読処理 ○ → MarkAsReadUseCase ● 現在のお知らせ一覧を返却する機能 ○ → NotificationRepository ● APIを経由したサーバーへの反映処理 ○ → NotificationServerDataStore 既存のコードをアクターを単位にして分割する 20

Slide 21

Slide 21 text

© GO Inc. 修正後クラス図 21

Slide 22

Slide 22 text

© GO Inc. 1. 既存のコードをアクターを単位にして分割する 2. 分割したコードの振る舞いをProtocolに抽象化しDI可能にする 3. それぞれに対してMockを利用してテストをする 再掲: 既存のコードにUnitTestを適用させる 22

Slide 23

Slide 23 text

© GO Inc. UnitTestのときにMockを作成しやすくするため、責務をProtocolに抽象化する 分割したコードの振る舞いをProtocolに抽象化しDI可能にする 23

Slide 24

Slide 24 text

© GO Inc. 修正後クラス図 24

Slide 25

Slide 25 text

© GO Inc. 1. 既存のコードをアクターを単位にして分割する 2. 分割したコードの振る舞いをProtocolに抽象化しDI可能にする 3. それぞれに対してMockを利用してテストをする 再掲: 既存のコードにUnitTestを適用させる 25

Slide 26

Slide 26 text

© GO Inc. UnitTestを書く 〜 MarkAsReadUseCaseImpl 26 protocol MarkAsReadUseCase { func execute(notification: Notification, completion: @escaping (() -> Void)) } struct MarkAsReadUseCaseImpl: MarkAsReadUseCase { let notificationRepository: NotificationRepository func execute(notification: Notification, completion: @escaping (() -> Void)) { notificationRepository.store(notification: readNotification, completion: completion) } }

Slide 27

Slide 27 text

© GO Inc. UnitTestを書く 〜 MarkAsReadUseCaseImpl 27 MarkAsReadUseCaseImplの役割は1つ 1. RepositoryのStore関数を呼び出す → 上記が正しく動作することをUnitTestで確認する

Slide 28

Slide 28 text

© GO Inc. UnitTestを書く 〜 MarkAsReadUseCaseImpl 28 1. NotificationRepositoryのMockを作る 2. MockをDIしてMarkAsReadUseCaseImplを初期化する 3. テスト用のNotificationを利用してmarkAsRead関数を呼び出す。 4. Repositoryの関数呼び出し回数と、取り扱っているデータが正しいことを確認 する

Slide 29

Slide 29 text

© GO Inc. NotificationRepositoryのMockを作る 29 struct FakeNotificationRepository: NotificationRepository { func getAllNotification() -> [Notification] { return [] } var storeCallCount = 0 var storeArguments: Notification! func store(notification: Notification, completion: @escaping (() -> Void)) { storeCallCount += 1 storeArguments = notification completion() } }

Slide 30

Slide 30 text

© GO Inc. let fakeRepository = FakeNotificationRepository() execute関数のテストを書く 30

Slide 31

Slide 31 text

© GO Inc. let fakeRepository = FakeNotificationRepository() let subject = MarkAsReadUseCaseImpl(notificationRepository: fakeRepository) execute関数のテストを書く 31

Slide 32

Slide 32 text

© GO Inc. let fakeRepository = FakeNotificationRepository() let subject = MarkAsReadUseCaseImpl(notificationRepository: fakeRepository) let fakeNotification = Notification(title: "テストお知らせ", isRead: false) subject.execute(notification: fakeNotification, completion: { in }) execute関数のテストを書く 32

Slide 33

Slide 33 text

© GO Inc. let fakeRepository = FakeNotificationRepository() let subject = MarkAsReadUseCaseImpl(notificationRepository: fakeRepository) let fakeNotification = Notification(title: "テストお知らせ", isRead: false) subject.execute(notification: fakeNotification, completion: { in }) expect(fakeRepository.storeCallCount).to(equal(1)) expect(fakeRepository.storeArguments).to(equal(fakeNotification)) execute関数のテストを書く 33

Slide 34

Slide 34 text

© GO Inc. UnitTestを書いたことによる副次的なメリット ● アクターをベースとした責務で切り分けたので、既存コードに対して修正を行っ た際の意図せぬ副作用のリスクを低減できた 34

Slide 35

Slide 35 text

© GO Inc. まとめ 04

Slide 36

Slide 36 text

© GO Inc. まとめ 36 ● UnitTestは詳細設計に対応するテストで、クラスや処理をテストする ● UnitTestをCI上でDailyで回すなどして問題の早期発見に活かすと良い ● 既存コードに適用する場合は、アクターという観点で既存コードを分解してから UnitTestを組む

Slide 37

Slide 37 text

© GO Inc. 参考資料 05

Slide 38

Slide 38 text

© GO Inc. まとめ 38 ● V字モデルとは ○ https://shiftasia.com/ja/column/v%E5%AD%97%E3%83%A2%E3%83 %87%E3%83%AB%E3%81%A8%E3%81%AF/ ● ホワイトボックステストとは ○ https://service.shiftinc.jp/column/4801/ ● solid+cqs+dry ○ https://speakerdeck.com/kgmyshin/solid-plus-cqs-plus-dry

Slide 39

Slide 39 text

文章・画像等の内容の無断転載及び複製等の行為はご遠慮ください。 © GO Inc.