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
良いユニットテストを書こう
Search
mototakatsu
December 20, 2024
Programming
13
3.7k
良いユニットテストを書こう
Ebisu.mobile #8 登壇資料です。
https://hey.connpass.com/event/335971/
mototakatsu
December 20, 2024
Tweet
Share
Other Decks in Programming
See All in Programming
密集、ドキュメントのコロケーション with AWS Lambda
satoshi256kbyte
0
190
ソフトウェアエンジニアの成長
masuda220
PRO
11
1.7k
Bedrock Agentsレスポンス解析によるAgentのOps
licux
3
850
2024年のWebフロントエンドのふりかえりと2025年
sakito
3
250
Djangoアプリケーション 運用のリアル 〜問題発生から可視化、最適化への道〜 #pyconshizu
kashewnuts
1
250
時計仕掛けのCompose
mkeeda
1
300
color-scheme: light dark; を完全に理解する
uhyo
5
390
Grafana Loki によるサーバログのコスト削減
mot_techtalk
1
130
第3回 Snowflake 中部ユーザ会- dbt × Snowflake ハンズオン
hoto17296
4
370
もう僕は OpenAPI を書きたくない
sgash708
5
1.8k
WebDriver BiDiとは何なのか
yotahada3
1
140
Amazon Bedrock Multi Agentsを試してきた
tm2
1
290
Featured
See All Featured
We Have a Design System, Now What?
morganepeng
51
7.4k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.5k
Why Our Code Smells
bkeepers
PRO
336
57k
Building an army of robots
kneath
303
45k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
10
1.3k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
1k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
30
2.2k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
Gamification - CAS2011
davidbonilla
80
5.1k
Transcript
© asken.inc 「良いユニットテスト」を書こう 2024/12/20 Ebisu.mobile #8 asken 高津 基暢
© asken.inc 2 自己紹介 株式会社asken Android Engineer / QA Engineer
Android Engineerとしてasken入社 Android開発は2012年から 元々テストが好きだったこともあって、 2024年からQA Engineer JSTQB FL保有 趣味はボードゲームと漫画とラジオ
© asken.inc 3 Agenda 1. なぜユニットテストを書く必要があるのか 2. 何を対象にユニットテストを書いているのか 3. 「良いユニットテスト」とは
4. 実際にどんな効果があったか
© asken.inc 4 1. なぜユニットテストを 書く必要があるのか
© asken.inc 5 Googleのテスト戦略 引用: https://developer.android.com/training/testing/fundamentals/strategies
© asken.inc 6 ユニットテストに期待される効果 • バグの早期発見 • リグレッションエラー(デグレ)の検出 • 設計改善
© asken.inc 7 効果を最大化するために • テストを書く範囲を決めて、カバレッジを上げる • 質が良いテストコードを書く
© asken.inc 8 2. あすけんAndroidでは何を対象に ユニットテストを書いているのか
© asken.inc 9 (前提)MVVM アーキテクチャを採用 Model, ViewModel(UiModel含む): ユニットテストを書いている View: ユニットテストを書いていない
コンポーネントテストを検討中 ユニットテストを書いているところ View ViewModel UiModel Model
© asken.inc 10 3. 「良いユニットテスト」と は 信頼性と可読性
© asken.inc 11 テストの結果が信頼できる 信頼できない = 実行するたびに結果が変わる(=不安定) テストの結果が不安定になる原因の例 • システム時刻を参照している
• データベースやSharedPreferencesを参照している
© asken.inc 12 テストの結果が信頼できる プロダクトコード: 正しい プロダクトコード: 誤り テスト結果: 成功
期待どおり 偽陰性 (検知漏れ) テスト結果: 失敗 偽陽性 (誤検知) 期待どおり 偽陽性、偽陰性のどちらも発生しない テストの結果が信頼できないと、 障害修正のコストが跳ね上がる!
© asken.inc 13 可読性を高めるために、以下の点を工夫する • 1つのテストケースで1つの観点をテストする • テストケース名でテストの意図がわかるようにする • AAAパターンまたはGherkin記法で整理する
• ループや条件分岐をなくし、上から下へ素直に読み下せるよ うにする • 文字列や数値をベタ書きする 可読性が高い
© asken.inc 14 読みにくいテストコード例 @Test fun validate_birthday() { val today
= LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) // 14歳の場合 (誕生日前日) は NG … assertFalse(... } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime {
© asken.inc 15 NG: 1つのテストケースで複数テストしている @Test fun validate_birthday() { val
today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) // 14歳の場合 (誕生日前日) は NG … assertFalse(... } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { 複数の確認項目が1つのテストケースにある
© asken.inc 16 OK: 1つのテストケースで1つの観点をテストする @Test fun validate_birthday_正常系() { val
today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) } @Test fun validate_birthday_異常系() { ... 確認項目ごとにテストケースを分割
© asken.inc 17 NG: 何を確認したいのかわからないテストケース名 @Test fun validate_birthday_正常系() { val
today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // 15歳の場合 (誕生日当日) は OK assertTrue(Birthday.validate(birthday, today)) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { ... 正常って何? 何がどうなっていれば正しい? テスト対象の関数名が テストケース名に含まれている
© asken.inc 18 OK: テストケース名でテストの意図がわかる @Test fun 15歳の誕生日当日は登録可能とする() { val
today = LocalDateTime.now() val birthday = getDateBefore15Years(today) assertTrue(Birthday.validate(birthday, today)) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { ...
© asken.inc 19 NG: テストケースの処理フローの説明がない @Test fun 15歳の誕生日当日は登録可能とする() { val
today = LocalDateTime.now() val birthday = getDateBefore15Years(today) assertTrue(Birthday.validate(birthday, today)) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { ... AAAパターンのコメントを追加して テストの流れを整理する
© asken.inc 20 OK: AAAパターンで処理フローを整理する @Test fun 15歳の誕生日当日は登録可能とする() { //
Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { Arrange / Act / Assert に分割して整理する
© asken.inc 21 NG: テストデータ作成を関数化している @Test fun 15歳の誕生日当日は登録可能とする() { //
Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = getDateBefore15Years(today) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } private fun getDateBefore15Years(today: LocalDateTime): LocalDateTime { 他のテストケースでも同じ処理を使って いるので関数化している
© asken.inc 22 OK: 上から下へ素直に読み下せるようにする @Test fun 15歳の誕生日当日は登録可能とする() { //
Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = today.minusYears(15) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } 他のテストケースと同じ処理であっても 関数の切り出しをしない
© asken.inc 23 NG: テストデータを計算している @Test fun 15歳の誕生日当日は登録可能とする() { //
Arrange // 15歳の誕生日を用意する val today = LocalDateTime.now() val birthday = today.minusYears(15) // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } システム時刻から今日が15歳の誕生日とな る日付を計算している
© asken.inc 24 OK: テストデータに必要な値をベタ書きする @Test fun 15歳の誕生日当日は登録可能とする() { //
Arrange // 今日と15歳の誕生日を用意する val today = SimpleDateFormat("yyyyMMdd").parse("20241201") val birthday = SimpleDateFormat("yyyyMMdd").parse("20091201") // Act val actual = Birthday.validate(birthday, today) // Assert assertTrue(actual) } “今日”の日付と、その日が15歳の誕生日と なる日をベタ書きする
© asken.inc 25 4. 実際にどんな効果があったか
© asken.inc 26 askenエンジニアが実感している効果 期待される効果を実際に体験できている • バグの早期発見 • リグレッションエラー(デグレ)の検出 •
設計改善
© asken.inc 27 予想外の効果 iOSエンジニアにテストコードの レビューをしてもらえるようになった これまでの対応によりKotlinに不慣れでも テストコードの内容は理解できる レビューしてもらった結果… iOS
- Android間の仕様差異を事前に検出できた!!
© asken.inc 28 We are hiring! askenで一緒に活躍してくれる方を募集しています!
© asken.inc 29 Thank you!