Slide 1

Slide 1 text

テストコードの観点から⾒た Sansanのアーキテクチャ変遷 Sansan株式会社 技術本部 Mobile Applicationグループ 原⽥拓眞 モバイルアプリ×良いテストコード

Slide 2

Slide 2 text

⾃⼰紹介 Sansan株式会社 技術本部 Mobile Applicationグループ 原⽥拓眞 - 2014年Sansan株式会社新卒⼊社 - 当初はC#/.NET FrameworkのWebエンジニア (WinFormsやWPFも触った) - 2018年末頃からAndroidに転向 - 当時kotlin書いた経験無し - 最近⽝を飼い始めました。

Slide 3

Slide 3 text

- Sansan Androidアプリのアーキテクチャの変遷とユニット テストの関係性 - Sansan モバイルアプリの「テスト」 - KotlinMultiplatformでの開発におけるTips 本⽇の内容

Slide 4

Slide 4 text

良いテストコードは 良いアーキテクチャから⽣まれる

Slide 5

Slide 5 text

Sansanのアーキテクチャの変遷を テストコードという観点から⾒ていきます

Slide 6

Slide 6 text

『単体テストの考え⽅/使い⽅(Vladimir Khorikov著)』によれば、 単体テストの⽬的は「ソフトウェア開発プロジェクトの成⻑を持続可能なものにする」で ある。 具体的には以下の4柱が満たられていることとされる。 - 退⾏(regression)に対する保護 - コードを変更した時のバグの発⾒しやすさ - コードの複雑さやビジネスとしての重要度も変数になる - リファクタリングへの耐性 - テストが失敗せずリファクタできる / コードが正しいのにテストが失敗することがない - 迅速なフィードバック - テストにかかる時間が短い - 保守のしやすさ - テストの読みやすさ / テストの実⾏のしやすさ 良いテストコード(ユニットテスト)

Slide 7

Slide 7 text

- MVPアーキテクチャ - Viewは、UI表⽰とUIの⼊出⼒ - Presenterは、画⾯ロジック - Modelは、ビジネスロジックやAPI, DBのデータ取得 - 2017年のアプリリニューアル時のアーキテクチャ Sansan Androidアプリのアーキテクチャ(⼀代⽬) Model View Presenter User event Request Update UI Result

Slide 8

Slide 8 text

- PresenterのテストにStub的なMockが多くて⾟い - Model-View間を繋いでいるので関わるクラスが多い - MockしているクラスのIFや実装を変えたとき、Mock側も対 応しなければならないのでMockが多いと⾟い - 状態管理の定義が曖昧 - Presenterで持つことも、Viewで持つことも - どこのテストに書かれているのか⾒つけにくい - PresenterやModelのクラスが⼤きくなりやすい - テストクラスも⼤きくなるので可読性が下がる MVP時代の課題(テストコード観点)

Slide 9

Slide 9 text

- 退⾏(regression)に対する保護 - そもそもバグを起こしやすい構造になっていた - リファクタリングへの耐性 - 低い - 迅速なフィードバック - 特筆すべきものはない - 保守のしやすさ - 可読性が低いため良くない 良いテストコードから考えると

Slide 10

Slide 10 text

アプリ開発の規模に アーキテクチャが合わなくなってきた

Slide 11

Slide 11 text

Sansan Androidアプリのアーキテクチャ(⼆代⽬) - Fluxアーキテクチャをベースに実装 - Fluxはmeta社が提唱した単⽅向データフローアーキテクチャ - Sansan Androidでは2019年から導⼊開始 よくインターネットで⾒る図↓ Action Dispatcher Store View Action

Slide 12

Slide 12 text

実際のクラスの関係性 Sansan Androidアプリのアーキテクチャ(⼆代⽬) ActionCreator Action View Store Dispatcher generate Dispatch Action notify observe Dispatchされた Actionに応じて Stateを更新し保持する

Slide 13

Slide 13 text

- クラスの責務がMVPより明確になった - ActionCreator: Stateを変更するためのActionを作る。 Actionの作成に必要なRepositoryの関数も呼び出す。 - Store: Stateの保持。また、Actionの通知を受けてStateを更新 する。 - View: Storeの保持しているStateを購読し、画⾯表⽰する。 - 責務が明確なので、どんなテストがどこに書かれているかも想像 しやすい - それぞれのクラスが⼩さくなった Fluxになったことで解決した課題(テストコード観点)

Slide 14

Slide 14 text

- 責務が明確になり - 退⾏(regression)に対する保護 > 重要なロジックにテストを集中しやすくなった - リファクタリングへの耐性 > 余計なStubが減ったためリファクタしても壊れにくくなった - 迅速なフィードバック > テストを書くべきコードに焦点を当てやすく、余計なテス トがない = 全体の実⾏速度は早い - 保守のしやすさ > プロダクションコードもテストコードも書きやすくなった 良いテストコードから考えると

Slide 15

Slide 15 text

Flux時代(⼆代⽬アーキテクチャ)の課題 - StoreはActionを受け取り、必要に応じて現在のStateとActionを 合成して新しいStateを⽣成‧保持していた。 - ⼤規模な画⾯を開発するとActionが⾮常に多く、Storeが⼤きく なり可読性が下がる、テストが書きにくいなどの弊害が⽣じた。 - その後導⼊したJetpackComposeとStateが親和しない事が あった。また、画⾯ロジックが散逸し、統⼀性の喪失や、テスト が書きにくいなどの問題が⽣じた。

Slide 16

Slide 16 text

より開発⽣産性を上げるため 更に改良を⾏う

Slide 17

Slide 17 text

実際のクラスの関係性(再掲) Sansan Androidアプリのアーキテクチャ(⼆代⽬) ActionCreator Action View Store Dispatcher generate Dispatch Action notify observe Dispatchされた Actionに応じて Stateを更新し保持する

Slide 18

Slide 18 text

実際のクラスの関係性(新) Sansan Androidアプリのアーキテクチャ(⼆代⽬改) ActionCreator Action View Store Dispatcher generate Dispatch Action notify observe StateMachine ViewPropertyFactory ViewProperty create create state

Slide 19

Slide 19 text

⼆代⽬アーキテクチャの課題の解決 - Fluxを少し拡張して、責務をさらに細分化 - 具体的には - Storeの役割から「Stateの⽣成」を分離 - 画⾯に直接バインドできる値を持つクラスを定義 > さらに⽣成ロジックも分離

Slide 20

Slide 20 text

- クラスの責務がさらに明確になり、よりテストしやすくなっ た - 各クラスの責務 - Store: Actionの通知を受け取る、Stateを保持する - StateMachine: 現在のStateと、Stateの更新に必要な情報 を受け取り新しいStateを返す - ViewPropertyFactory: Stateを元に、画⾯上で扱いやすい 形のオブジェクトを作る(ビューロジック) Flux(⼆代⽬改)で解決した課題(テストコード観点)

Slide 21

Slide 21 text

- さらに責務が細分化されることで - 退⾏(regression)に対する保護 > より重要なロジックにテストを集中しやすくなった - 迅速なフィードバック > より重要なテストに絞り込むことが出来た - 保守のしやすさ > さらにプロダクションコードもテストコードも書きやすく なった 良いテストコードから考えると

Slide 22

Slide 22 text

アーキテクチャが良くなると テストコードも良くなる

Slide 23

Slide 23 text

Sansanモバイルアプリに関わる「テスト」

Slide 24

Slide 24 text

- ユニットテスト - 各クラス‧関数をテストするコード - 基本的には書く⽅針 - PRを作るとCIで実⾏もされる(通らないとマージできない) - 結合テスト - 開発案件の終了間際、QAチームにより要件‧仕様を満たしている かどうかを⼿動テストする - リグレッションテスト - リリース前に既存機能にデグレが発⽣していないかをテストする - QAチームによる⼿動 + MagicPod Sansanモバイルアプリの「テスト」

Slide 25

Slide 25 text

- ユニットテストに求めているもの - その関数の正常系、異常系処理が期待通りの動きか > 前提、内容、結果を意識している(AAA) > 「正しく動くこと」と書くと怒られる > 仕様のドキュメント的な側⾯もある - リファクタリングの前後で関数の振る舞いが変わって ないことを確認できる Sansanモバイルアプリの「テスト」

Slide 26

Slide 26 text

- テストのカバレッジは明確には追ってない① - PRに2⼈のレビュアーからApproveを貰う必要がある > テストコードを全く書いてないと指摘される > StateMachineやViewPropertyFactoryのテストはまず 書かれる - リリース不具合が少ない > 前Qでリリースしたものでは⼤きな不具合なし > QAチームでの結合テストやリグレッションテストもある > 不具合検知率⾃体もテストケースに対して0.5%程度 (ケース数1803件に対して) Sansanモバイルアプリの「テスト」

Slide 27

Slide 27 text

- テストのカバレッジは明確には追ってない② - ほとんどの画⾯ではViewPropertyFactory, StateMachine の2つに対してテストコードを書いている > 全体に対しての網羅率は低いが、不具合は少なく抑え られている > 画⾯の状態に関するテストしたい重要な知識を持つ クラスを切り出してテストしやすくしている Sansanモバイルアプリの「テスト」

Slide 28

Slide 28 text

KMPに関するTips

Slide 29

Slide 29 text

KotlinMultiplatform - Sansanでは最近導⼊し始めた - iOS / AndroidのView以外の部分の共通化を図る - ビジネスロジックの共通化によりプラットフォームごと の仕様差異を減らす - リソースの効率化

Slide 30

Slide 30 text

- Androidで⼀般的なDagger/HiltはKMP未対応 - KoinがKMP環境では⼀般的なDIライブラリ - Hiltと違ってコンパイルエラーにならず、ランタイム クラッシュする - テストでのDIもサポートしているので、テストコードで DIの結果が期待通りであるかをチェックできる 期待通りDIされているかをテストで確認する

Slide 31

Slide 31 text

勉強会やります

Slide 32

Slide 32 text

After Kotlin Fest 2024 LT Night @Sansan(LT登壇枠あり) - テーマ - Kotlinの内容であればなんでもOKのLT会 ■ Kotlin 2.0の話も予定 - ⽇時 - 2024/07/08 19:00 - - 形式 - オンライン/オフライン併催

Slide 33

Slide 33 text

- テーマ - モバイルアプリ、モバイルアプリ開発関連ならなんでも ■ iOS / Android問わず - ⽇時 - 2024/07/10 19:30 - - 形式 - オンライン/オフライン併催 Sansanモバイル勉強会 vol.1