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
4.2k
良いユニットテストを書こう
Ebisu.mobile #8 登壇資料です。
https://hey.connpass.com/event/335971/
mototakatsu
December 20, 2024
Tweet
Share
Other Decks in Programming
See All in Programming
モデル駆動設計をやってみようワークショップ開催報告(Modeling Forum2025) / model driven design workshop report
haru860
0
290
「コードは上から下へ読むのが一番」と思った時に、思い出してほしい話
panda728
PRO
39
26k
愛される翻訳の秘訣
kishikawakatsumi
3
350
Pythonではじめるオープンデータ分析〜書籍の紹介と書籍で紹介しきれなかった事例の紹介〜
welliving
3
620
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
390
リリース時」テストから「デイリー実行」へ!開発マネージャが取り組んだ、レガシー自動テストのモダン化戦略
goataka
0
150
Go コードベースの構成と AI コンテキスト定義
andpad
0
140
0→1 フロントエンド開発 Tips🚀 #レバテックMeetup
bengo4com
0
400
【卒業研究】会話ログ分析によるユーザーごとの関心に応じた話題提案手法
momok47
0
130
從冷知識到漏洞,你不懂的 Web,駭客懂 - Huli @ WebConf Taiwan 2025
aszx87410
2
3.1k
AIコーディングエージェント(Gemini)
kondai24
0
280
Canon EOS R50 V と R5 Mark II 購入でみえてきた最近のデジイチ VR180 事情、そして VR180 静止画に活路を見出すまで
karad
0
140
Featured
See All Featured
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.3k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
1
270
Highjacked: Video Game Concept Design
rkendrick25
PRO
0
250
Stop Working from a Prison Cell
hatefulcrawdad
273
21k
Amusing Abliteration
ianozsvald
0
72
How to train your dragon (web standard)
notwaldorf
97
6.5k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
70
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
1k
How GitHub (no longer) Works
holman
316
140k
Ethics towards AI in product and experience design
skipperchong
1
140
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
130
How to Ace a Technical Interview
jacobian
281
24k
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!