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
Proxmoxをまとめて管理できるコンソール作ってみました
karugamo
1
410
Agent Rules as Domain Parser
yodakeisuke
1
330
AIにコードを生成するコードを作らせて、再現性を担保しよう! / Let AI generate code to ensure reproducibility
yamachu
7
6k
〜可視化からアクセス制御まで〜 BigQuery×Looker Studioで コスト管理とデータソース認証制御する方法
cuebic9bic
1
260
インターフェース設計のコツとツボ
togishima
2
480
iOSアプリ開発もLLMで自動運転する
hiragram
6
2.1k
Reactive Thinking with Signals, Resource API, and httpResource @Devm.io Angular 20 Launch Party
manfredsteyer
PRO
0
130
從零到一:搭建你的第一個 Observability 平台
blueswen
0
210
メモリリークが発生した時にpprofを使用して原因特定した話
zono33lhd
0
100
「MCPを使ってる人」が より詳しくなるための解説
yamaguchidesu
0
590
事業KPIを基に価値の解像度を上げる
nealle
0
200
OpenTelemetry + LLM = OpenLLMetry!?
yunosukey
2
400
Featured
See All Featured
Art, The Web, and Tiny UX
lynnandtonic
298
21k
The Power of CSS Pseudo Elements
geoffreycrofte
76
5.8k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.7k
Product Roadmaps are Hard
iamctodd
PRO
53
11k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
Git: the NoSQL Database
bkeepers
PRO
430
65k
Building Applications with DynamoDB
mza
95
6.4k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
48
5.4k
Java REST API Framework Comparison - PWX 2021
mraible
31
8.6k
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!