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
740
Kotlin/Androidでテスト駆動開発をはじめよう
magicpodさん主催のイベント「モバイルアプリ開発における良いテストコードの考え方」のセッションスライドです。
hiro
June 23, 2024
Tweet
Share
More Decks by hiro
See All by hiro
Compose UIテストを使った統合テスト
hiroaki404
0
160
Other Decks in Programming
See All in Programming
Domain-Driven Design (Tutorial)
hschwentner
13
22k
CloudNativePGを布教したい
nnaka2992
0
120
sappoRo.R #12 初心者セッション
kosugitti
0
280
PHPカンファレンス名古屋2025 タスク分解の試行錯誤〜レビュー負荷を下げるために〜
soichi
1
720
Boos Performance and Developer Productivity with Jakarta EE 11
ivargrimstad
0
430
一休.com のログイン体験を支える技術 〜Web Components x Vue.js 活用事例と最適化について〜
atsumim
0
1.1k
たのしいSocketのしくみ / Socket Under a Microscope
coe401_
8
1.4k
Kotlinの開発でも AIをいい感じに使いたい / Making the Most of AI in Kotlin Development
kohii00
5
1.4k
pylint custom ruleで始めるレビュー自動化
shogoujiie
0
160
5分で理解する SOLID 原則 #phpcon_nagoya
shogogg
1
330
iOSでQRコード生成奮闘記
ktcryomm
2
110
.NET Frameworkでも汎用ホストが使いたい!
tomokusaba
0
200
Featured
See All Featured
Why Our Code Smells
bkeepers
PRO
336
57k
Fontdeck: Realign not Redesign
paulrobertlloyd
83
5.4k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.2k
Code Review Best Practice
trishagee
67
18k
The Cult of Friendly URLs
andyhume
78
6.2k
Visualization
eitanlees
146
15k
Adopting Sorbet at Scale
ufuk
74
9.2k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
46
2.3k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Facilitating Awesome Meetings
lara
52
6.2k
The Cost Of JavaScript in 2023
addyosmani
47
7.4k
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をやりづらい場合もあるので、厳密にやったり毎回必ずやる必要はない •
技術をあまり理解できていなときなど • 既存のコードがあるところなどやりやすいところからやってみましょう