$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Kotlin/Androidでテスト駆動開発をはじめよう
Search
hiro
June 23, 2024
Programming
1
950
Kotlin/Androidでテスト駆動開発をはじめよう
magicpodさん主催のイベント「モバイルアプリ開発における良いテストコードの考え方」のセッションスライドです。
hiro
June 23, 2024
Tweet
Share
More Decks by hiro
See All by hiro
KoogではじめるAIエージェント開発
hiroaki404
1
540
Compose Navigation実装の見通しを良くする
hiroaki404
0
310
Compose UIテストを使った統合テスト
hiroaki404
0
310
Other Decks in Programming
See All in Programming
分散DBって何者なんだ... Spannerから学ぶRDBとの違い
iwashi623
0
160
2025 컴포즈 마법사
jisungbin
1
170
関数実行の裏側では何が起きているのか?
minop1205
1
400
Microservices Platforms: When Team Topologies Meets Microservices Patterns
cer
PRO
1
840
AI時代もSEOを頑張っている話
shirahama_x
0
210
手が足りない!兼業データエンジニアに必要だったアーキテクチャと立ち回り
zinkosuke
0
250
GeistFabrik and AI-augmented software development
adewale
PRO
0
230
TUIライブラリつくってみた / i-just-make-TUI-library
kazto
1
290
Micro Frontendsで築いた 共通基盤と運用の試行錯誤 / Building a Shared Platform with Micro Frontends: Operational Learnings
kyntk
1
1.9k
全員アーキテクトで挑む、 巨大で高密度なドメインの紐解き方
agatan
8
17k
手軽に積ん読を増やすには?/読みたい本と付き合うには?
o0h
PRO
1
130
モデル駆動設計をやってみよう Modeling Forum2025ワークショップ/Let’s Try Model-Driven Design
haru860
0
220
Featured
See All Featured
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.1k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Code Reviewing Like a Champion
maltzj
527
40k
Building Applications with DynamoDB
mza
96
6.8k
It's Worth the Effort
3n
187
29k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Navigating Team Friction
lara
191
16k
The Art of Programming - Codeland 2020
erikaheidi
56
14k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Writing Fast Ruby
sferik
630
62k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
1.6k
Transcript
MAZDA MOTOR CORPORATION Confidential 納庄 宏明 マツダ株式会社 Kotlin/Androidでテスト駆動開発をはじめよう モバイルアプリ開発における良いテストコードの考え⽅ 2024/6/24
⾃⼰紹介 •納庄 宏明 Nosho Hiroaki •マツダ株式会社, Android Engineer 2022〜 •Mazda開発チームについて
•ITチームは東京本社・広島本社の2拠点+リモート •中途採⽤に積極的 •Mazdaアプリの紹介 •MyMazda: コネクテッドサービスアプリ など⾃動⾞関係のiOS/Androidアプリを公開
本セッションの⽬的 •TDDを⼀つの⼿段として提⽰する •Androidの良いユニットテストを考える
参考⽂献・リンク 書籍 • 『テスト駆動開発』KentBeck著 オーム社 • 『Googleのソフトウェアエンジニアリング』 オライリー・ジャパン • 『単体テストの考え⽅/使い⽅』
Vladimir Khorikov著 マイナビ Webリンク • Android developer guide • アプリ アーキテクチャ https://developer.android.com/topic/architecture/intro • Android でアプリをテストする https://developer.android.com/training/testing • サンプル • Now in Android、architecture-samplesなどの公式のサンプル 注意点 • TDDやテストには⾊々な考え⽅があります。個⼈的な⾒解をまとめたものです。 スライド 補⾜
TDDとは?
ある開発現場の悩み テストがない そもそもテスト作りづらい 機能作らなきゃ、リファクタリン グしなきゃ、で頭がいっぱい TDD テストがある テストが作りやすい やるべきことに集中でき、迷いが ない
TDDとは? •テスト駆動開発(Test-Driven Development 略してTDD) • テスト駆動開発 (てすとくどうかいはつ、英: test-driven development; TDD)
とは、プログラム開発⼿法の ⼀種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと⾔う) 、そのテストが動作する必要最低限な実装をとりあえず⾏なった後、コードを洗練させる、という短い ⼯程を繰り返すスタイルである。 • Wikipedia「テスト駆動開発」https://ja.wikipedia.org/wiki/テスト駆動開発 •要は先にテストを書いてから実装することが⼤きな特徴の開発 スタイル •統合テストもTDDで⾏い、構造化することも可能
•テストすることが開発サイクルに組み込まれる •やるべきことに注⼒でき、開発が効率化される •これから開発するコードの仕様を作成できる •プロダクションコードがテストしやすい形になる •など TDDのメリット テストがある テストが作りやすい やるべきことに集中でき、迷いが ない
TDDの実践
TDDで開発する機能 • 野⿃図鑑アプリ • ViewModelの実装とユニットテスト • Googleのアーキテキチャガイドに沿ったもの • Repositoryを通してリモートのAPIからデータを取得 •
機能:野⿃の⼀覧を取得する (UIやデータソースのプロダクションコードは作りません) github: https://github.com/hiroaki404/tddKotlin (追加の実例もあります)
Red Green Refactoring •Red •失敗するテストを書く •Green •テストを通るようにプロダクションコー ドを書く。テストを通すことだけに集中 する •Refactoring
•テストを通すために発⽣した重複を除去 する。コードを整える Red Green Refactor
Red
red-green-refactoring 失敗するテストを書く プロダクションコードを書かずに、 テストから書く class ExampleViewModelTest { }
プロダクションコードを書かずに、 テストから書く red-green-refactoring 失敗するテストを書く class ExampleViewModelTest { private lateinit var
exampleViewModel: ExampleViewModel @Before fun setup() { exampleViewModel = ExampleViewModel() } }
class ExampleViewModelTest { private lateinit var exampleViewModel: ExampleViewModel @Before fun
setup() { exampleViewModel = ExampleViewModel() } @Test fun `⿃の⼀覧を取得できる`() = runTest { // Given // When // Then } } red-green-refactoring 失敗するテストを書く Given-When-Then構⽂ Given(前提条件)-When(操作)-Then(結果) の形式によりテストコードの可読性を ⾼める プロダクションコードを書かずに、 テストから書く
class ExampleViewModelTest { private lateinit var exampleViewModel: ExampleViewModel @Before fun
setup() { exampleViewModel = ExampleViewModel() } @Test fun `⿃の⼀覧を取得できる`() = runTest { // Given // When // Then assertEquals( ExampleUiState( birds = listOf( suzume, tsubame, magamo ) ), exampleViewModel.uiState.value ) } } red-green-refactoring 失敗するテストを書く 前提条件って何? 野⿃取得が取得される前の、 ローディング状態 初期状態のテストもあるとよい が、今回はパス Given-When-Then構⽂ Given(前提条件)-When(操作)-Then(結果) の形式によりテストコードの可読性を ⾼める プロダクションコードを書かずに、 テストから書く
red-green-refactoring 失敗するテストを書く class ExampleViewModelTest { private lateinit var exampleViewModel: ExampleViewModel
@Before fun setup() { exampleViewModel = ExampleViewModel() } @Test fun `⿃の⼀覧を取得できる`() = runTest { // Given // When exampleViewModel.refresh() // Then assertEquals( ExampleUiState( birds = listOf( suzume, tsubame, magamo ) ), exampleViewModel.uiState.value ) } }
class ExampleViewModelTest { private lateinit var exampleViewModel: ExampleViewModel @Before fun
setup() { exampleViewModel = ExampleViewModel() } @Test fun `⿃の⼀覧を取得できる`() = runTest { // Given // When exampleViewModel.refresh() // Then assertEquals( ExampleUiState( birds = listOf( suzume, tsubame, magamo ) ), exampleViewModel.uiState.value ) } } Redを確認しました。 これが第⼀歩です red-green-refactoring 失敗するテストを書く class ExampleViewModelTest { private lateinit var exampleViewModel: ExampleViewModel @Before fun setup() { exampleViewModel = ExampleViewModel() } @Test fun `⿃の⼀覧を取得できる`() = runTest { // Given // When exampleViewModel.refresh() // Then assertEquals( ExampleUiState( birds = listOf( suzume, tsubame, magamo ) ), exampleViewModel.uiState.value ) } }
Green
class ExampleViewModel: ViewModel() { private val _uiState = MutableStateFlow(ExampleUiState()) val
uiState: StateFlow<ExampleUiState> = _uiState.asStateFlow() fun refresh() { } } red-green-refactoring テストを通るようにプロ ダクションコードを書く Green
class ExampleViewModel: ViewModel() { private val _uiState = MutableStateFlow(ExampleUiState()) val
uiState: StateFlow<ExampleUiState> = _uiState.asStateFlow() fun refresh() { viewModelScope.launch { _uiState.update { it.copy( birds = listOf(suzume, tsubame, magamo) ) } } } } red-green-refactoring テストを通るようにプロ ダクションコードを書く Green この時点では罪を犯し とにかく最速でGreenを⽬指す
Refactoring
Refactoring class ExampleViewModel: ViewModel() { private val _uiState = MutableStateFlow(ExampleUiState())
val uiState: StateFlow<ExampleUiState> = _uiState.asStateFlow() fun refresh() { viewModelScope.launch { _uiState.update { it.copy( birds = listOf(suzume, tsubame, magamo) ) } } } } red-green-refactoring テストを通すために発⽣ した重複を除去する。 コードを整える テストのexpectとrefresh内部に 重複があったので、Repositoryか ら取得するように変更
Refactoring @HiltViewModel class ExampleViewModel @Inject constructor( private val repository: ExampleRepository
) : ViewModel() { private val _uiState = MutableStateFlow(ExampleUiState()) val uiState: StateFlow<ExampleUiState> = _uiState.asStateFlow() fun refresh() { viewModelScope.launch { val birds = repository.getBirds() _uiState.update { it.copy( birds = birds ) } } } } red-green-refactoring テストを通すために発⽣ した重複を除去する。 コードを整える テストのexpectとrefresh内部に 重複があったので、Repositoryか ら取得するように変更 テストからはフェイクを使う ※フェイクとは、下記のような ⾃作のテストダブル
TDDのメリット
TDDのメリット •テストすることが開発サイクルに組み込まれる •やるべきことに注⼒でき、開発が効率化される •これから開発するコードの仕様を作成できる •プロダクションコードがテストしやすい形になる •など •やるべきことに注⼒でき、開発が効率化される •これから開発するコードの仕様を作成できる 作業を細かいステッ プに分割して集中
⾃信を持ってリファ クタリング コードを書く前に 仕様を整理できる テストがある テストが作りやすい やるべきことに集中でき、迷いが ない
TDDのメリット •テストすることが開発サイクルに組み込まれる •やるべきことに注⼒でき、開発が効率化される •これから開発するコードの仕様を作成できる •プロダクションコードがテストしやすい形になる •など テストがある テストが作りやすい やるべきことに集中でき、迷いが ない
TDDをやらなかった場合、動けば良いで書いてしまいがち TDDでやらなくても慣れで書けるようになるが、意識が⾼まる initブロックを使うと 初期状態をテストしづらい Repositoryを直接呼び出してしまう フェイクを差し込めるようにする べき NG例です
ViewModelをテスタブルにしよう •状態の公開を読み取り専⽤のuiStateだけに限る •テストではuiStateをアサートすることに注⼒できる •Context、Activity、Fragmentに依存しない •Interfaceに依存し、フェイクを使えるようにしよう •DIのHiltを使う •複雑になってきたらUseCase等に処理を分割する
Flowを使った形に直そう •Flowを使うとオペレータが豊富に使えたり、制御もテストもし やすい
Flowはどうテストするの?
Flowはどうテストするの? • Turbineがおすすめ • Flowのテストが書きやすい • suspend関数も扱いやすくなる
TDDのデメリット
TDDのデメリット •それなりに労⼒がかかる •テストを書くのが⾜かせになり、後回しにしがち ->労⼒にならないために、どうするか?
AIの⽀援を使えば楽できる • github copilotがあれば、テスト作成をラクにできる テストを書くには精度が⾼く、 コード作成の補助として使える 他に • Android Studioのcode
template機能の設定で ボイラーコードを展開する • Refactor機能などでメソッド等の作成や Interfaceの抽出の⾃動化
•TDDを実施するだけで良いユニットテストが書けるわけではない •それなりに労⼒がかかる •テストを書くのが⾜かせになり、後回しにしがち ->労⼒にならないために、どうするか? TDDのデメリット
TDDの意識外でがんばること • 明快なテストを書く。失敗したときにすぐアクションできるようにする • テストケースを眺めただけで失敗した原因がわかるとよい • 安易に共通値や共通化をしすぎない • Truthライブラリなどでアサーションの⾒通しを良くする •
モックよりフェイクを使う(Googleも推奨) • 実⾏時間、忠実度の⾯でフェイクが良く、変更にも強い • 忠実度に関して、モック<フェイク<実際のクラス • フェイクは適切にメンテする必要がある • (今回はRepositoryにフェイクを使いましたが、 実際のクラスが使えるなら使ったほうが忠実度が上がる) • TDDをやるなら依存の少ないクラスから作ると、⽤意すべきテストダブルが減る
TDDの意識外でがんばること • TDDであれば、⾼速化は特に重要 • ⼩さなステップのなかでテストをたくさん実⾏するため • マルチモジュール化、並列化 • 保守コストや忠実度、速度などとのバランスを考える •
複雑なViewModel、ドメイン、ユーティリティのテストを重視 • カバレッジにとらわれすぎないこと • 必ずしもクラスやメソッドごとにテストするわけではない • チームで作るなら、レイヤーごとに分けてルール化など • (注)安全性関わる部分など重⼤な部分は⾼いカバレッジを⽬指します • UIのリグレッション検知ならスクリーンショットテストがおすすめ • 保守コストが低い • Roborazzi 、公式のスクリーンショット • UIテストをやるなら実⾏速度と保守コストのバランスで判断する • E2Eテストは忠実度が⾼く、バグの検出⼒も⾼い • ユニットテストと補完しあう E2E 統合テスト ユニットテスト コスト 忠実性 速度 決定性
おわりに • TDDをやってどうなった? • 開発効率があがった • テストへの意識が変わる • TDDをやりづらい場合もあるので、厳密にやったり毎回必ずやる必要はない •
技術をあまり理解できていなときなど • 既存のコードがあるところなどやりやすいところからやってみましょう