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
4k
良いユニットテストを書こう
Ebisu.mobile #8 登壇資料です。
https://hey.connpass.com/event/335971/
mototakatsu
December 20, 2024
Tweet
Share
Other Decks in Programming
See All in Programming
おやつのお供はお決まりですか?@WWDC25 Recap -Japan-\(region).swift
shingangan
0
140
Agentic Coding: The Future of Software Development with Agents
mitsuhiko
0
120
Startups on Rails in Past, Present and Future–Irina Nazarova, RailsConf 2025
irinanazarova
0
220
Flutterで備える!Accessibility Nutrition Labels完全ガイド
yuukiw00w
0
170
状態遷移図を書こう / Sequence Chart vs State Diagram
orgachem
PRO
2
190
顧客の画像データをテラバイト単位で配信する 画像サーバを WebP にした際に起こった課題と その対応策 ~継続的な取り組みを添えて~
takutakahashi
4
1.1k
オンコール⼊⾨〜ページャーが鳴る前に、あなたが備えられること〜 / Before The Pager Rings
yktakaha4
1
900
The Modern View Layer Rails Deserves: A Vision For 2025 And Beyond @ RailsConf 2025, Philadelphia, PA
marcoroth
2
680
PostgreSQLのRow Level SecurityをPHPのORMで扱う Eloquent vs Doctrine #phpcon #track2
77web
2
580
システム成長を止めない!本番無停止テーブル移行の全貌
sakawe_ee
1
310
GPUを計算資源として使おう!
primenumber
1
210
ペアプロ × 生成AI 現場での実践と課題について / generative-ai-in-pair-programming
codmoninc
2
21k
Featured
See All Featured
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
830
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
53
2.9k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
29
9.6k
Fantastic passwords and where to find them - at NoRuKo
philnash
51
3.3k
The World Runs on Bad Software
bkeepers
PRO
69
11k
Git: the NoSQL Database
bkeepers
PRO
430
65k
Reflections from 52 weeks, 52 projects
jeffersonlam
351
21k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
130
19k
4 Signs Your Business is Dying
shpigford
184
22k
The Invisible Side of Design
smashingmag
301
51k
Six Lessons from altMBA
skipperchong
28
3.9k
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!