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
990
Compose Navigation実装の見通しを良くする
hiroaki404
0
340
Compose UIテストを使った統合テスト
hiroaki404
0
330
Other Decks in Programming
See All in Programming
AIの誤りが許されない業務システムにおいて“信頼されるAI” を目指す / building-trusted-ai-systems
yuya4
7
4.3k
Graviton と Nitro と私
maroon1st
0
160
Vibe codingでおすすめの言語と開発手法
uyuki234
0
170
Denoのセキュリティに関する仕組みの紹介 (toranoana.deno #23)
uki00a
0
220
Findy AI+の開発、運用におけるMCP活用事例
starfish719
0
2.1k
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
690
perlをWebAssembly上で動かすと何が嬉しいの??? / Where does Perl-on-Wasm actually make sense?
mackee
0
320
QAフローを最適化し、品質水準を満たしながらリリースまでの期間を最短化する #RSGT2026
shibayu36
0
1.8k
Claude Codeの「Compacting Conversation」を体感50%減! CLAUDE.md + 8 Skills で挑むコンテキスト管理術
kmurahama
1
720
SQL Server 2025 LT
odashinsuke
0
140
AI 駆動開発ライフサイクル(AI-DLC):ソフトウェアエンジニアリングの再構築 / AI-DLC Introduction
kanamasa
11
5.2k
dchart: charts from deck markup
ajstarks
3
960
Featured
See All Featured
Imperfection Machines: The Place of Print at Facebook
scottboms
269
13k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.3k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
41
The Illustrated Children's Guide to Kubernetes
chrisshort
51
51k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
2
3.9k
Abbi's Birthday
coloredviolet
0
4.3k
Visualization
eitanlees
150
16k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.1k
Odyssey Design
rkendrick25
PRO
0
460
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
22k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
46
A designer walks into a library…
pauljervisheath
210
24k
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をやりづらい場合もあるので、厳密にやったり毎回必ずやる必要はない •
技術をあまり理解できていなときなど • 既存のコードがあるところなどやりやすいところからやってみましょう