Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Kotlin/Androidでテスト駆動開発をはじめよう
Search
hiro
June 23, 2024
Programming
1
990
Kotlin/Androidでテスト駆動開発をはじめよう
magicpodさん主催のイベント「モバイルアプリ開発における良いテストコードの考え方」のセッションスライドです。
hiro
June 23, 2024
Tweet
Share
More Decks by hiro
See All by hiro
KoogではじめるAIエージェント開発
hiroaki404
2
950
Compose Navigation実装の見通しを良くする
hiroaki404
0
330
Compose UIテストを使った統合テスト
hiroaki404
0
330
Other Decks in Programming
See All in Programming
「コードは上から下へ読むのが一番」と思った時に、思い出してほしい話
panda728
PRO
39
26k
Claude Codeの「Compacting Conversation」を体感50%減! CLAUDE.md + 8 Skills で挑むコンテキスト管理術
kmurahama
1
700
Basic Architectures
denyspoltorak
0
160
令和最新版Android Studioで化石デバイス向けアプリを作る
arkw
0
470
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
440
フルサイクルエンジニアリングをAI Agentで全自動化したい 〜構想と現在地〜
kamina_zzz
0
340
AIで開発はどれくらい加速したのか?AIエージェントによるコード生成を、現場の評価と研究開発の評価の両面からdeep diveしてみる
daisuketakeda
1
240
2年のAppleウォレットパス開発の振り返り
muno92
PRO
0
180
ELYZA_Findy AI Engineering Summit登壇資料_AIコーディング時代に「ちゃんと」やること_toB LLMプロダクト開発舞台裏_20251216
elyza
2
920
C-Shared Buildで突破するAI Agent バックテストの壁
po3rin
0
430
QAフローを最適化し、品質水準を満たしながらリリースまでの期間を最短化する #RSGT2026
shibayu36
0
1.2k
DevFest Android in Korea 2025 - 개발자 커뮤니티를 통해 얻는 가치
wisemuji
0
180
Featured
See All Featured
Site-Speed That Sticks
csswizardry
13
1k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.6k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
0
1.1k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
0
1k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
93
How to Think Like a Performance Engineer
csswizardry
28
2.4k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
1
1.4k
Odyssey Design
rkendrick25
PRO
0
450
Large-scale JavaScript Application Architecture
addyosmani
515
110k
The #1 spot is gone: here's how to win anyway
tamaranovitovic
1
880
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
36
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
0
2.3k
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をやりづらい場合もあるので、厳密にやったり毎回必ずやる必要はない •
技術をあまり理解できていなときなど • 既存のコードがあるところなどやりやすいところからやってみましょう