Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Androidテストハンズオン: テストのないアプリにテストを書こう編 / handson-w...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
tkmnzm
April 11, 2019
Programming
4.7k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Androidテストハンズオン: テストのないアプリにテストを書こう編 / handson-write-tests-for-app-without-test
tkmnzm
April 11, 2019
More Decks by tkmnzm
See All by tkmnzm
AndroidアプリのUIバリエーションをあの手この手で確認する / Check UI variations of Android apps by various means
tkmnzm
1
1.7k
Androidアプリの良いユニットテストを考える / Thinking about good unit tests for Android apps
tkmnzm
5
10k
Google I:O 2023 Androidの自動テストアップデートまとめ / Google I:O 2023 Android Testing Update Recap
tkmnzm
0
720
コルーチンのエラーをテストするためのTips / Tips for testing Kotlin Coroutine errors
tkmnzm
0
1.3k
Androidのモダンな技術選択にあわせて自動テストも アップデートしよう / Update your automated tests to match Android's modern technology choices
tkmnzm
3
2.6k
SWET dev-vitalチームによるプロジェクトの健康状態可視化の取り組み / SWET dev-vital team's efforts to visualize the health of the project
tkmnzm
1
1.4k
モバイルアプリテスト入門 / Getting Started with Mobile App Testing
tkmnzm
1
660
25分で作るAndroid Lint / Android Lint made in 25 minutes
tkmnzm
0
1.1k
2年半ぶりのプロダクト開発であらためて感じた自動テストの大切さ / realized the importance of automatic testing with product development for the first time in two and a half years
tkmnzm
1
890
Other Decks in Programming
See All in Programming
[2026-09-26]空論ジェネリックプロセス~テスト資産とAIで紡ぐ、再現可能なパフォーマンスチューニングの話~
tosite
0
260
【加筆修正版】Laravel のアプリケーションをどこにデプロイするか #phpcon_ehime
akase244
0
150
Ghostty + Neovimで作る 透明でカッコ良い開発環境
j341nono
0
150
Jetpack Compose Mechanisms
skydoves
2
390
カツオ、ご期待ください
suneo3476
0
150
ゲームコントローラやキーボードのファームウェアをSwiftで書く
kishikawakatsumi
1
280
The Rails Doctrine Decade
koic
2
500
[ハンズオン]AIへの指示だけで「五目並べ」を作ってみよう
satoshi256kbyte
1
350
Rでドレミ/Do-Re-Mi_with_R
florets1
0
120
AWS CDKのカスタムリソースでContinuum(旧Security Agent)を実装した話 | Implementing Continuum (formerly Security Agent) via AWS CDK Custom Resource
akihisaikeda
2
140
すこし踏み込む CancellationToken
htkym
2
1.6k
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
310
Featured
See All Featured
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
WENDY [Excerpt]
tessaabrams
14
40k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
470
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
610
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
920
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
Utilizing Notion as your number one productivity tool
mfonobong
4
610
Skip the Path - Find Your Career Trail
mkilby
1
240
Transcript
Androidテストハンズオン テストのないアプリにテストを書 こう編
今日の目標 • テストが書きにくいアプリを改善する体験をしてもらい 実プロジェクトでトライする取っ掛かりにする 2
アジェンダ • レガシーコードを改善しよう • レガシーコード改善に役立つ知識 − テストダブル − レガシーコード改善テクニック •
テストのないアプリにテストを書こう 3
レガシーコードを改善しよう 4
社内テスト実施状況アンケートの結果 • ユニットテストを「すでに導入しているが、もっと力をいれていき たい」「導入したいが、できていない」を合計して約75% • ユニットテストにおける課題として、50%が「テストが書きにくい 設計になっている」と回答 5
テストが書きにくい設計になっている • 新規に実装する − TDD等の手法を駆使しつつ、テストが書きやすい設計にする • 既存のコードはどうする? − ごっそり書き直す VS
地道に改善を重ねる − ごっそり書き直すチャンスを得られるのは稀。そして成功する保証はない。 6
地道に改善を重ねる • レガシーコード改善のすすめ − レガシーコードとは、単にテストのないコード(レガシーコード改善ガイドより) − テストを実装しながらテストが書きにくい設計を改善していく • 改善をしないとどうなる? −
レガシーコードに変更を加えていくことで、自分自身もレガシーコードを積み上 げてしまう悲劇 7
レガシーコード改善のアプローチ • 保護して変更する − 変更する箇所をテストで保護をした上で安全にリファクタリング − ユニットテストは開発中に素早くフィードバックを得られる − このアプローチが辛い場合、手動での動作確認も代替案として有効 8
レガシーコード改善手順 1. 変更点を洗い出す 2. テストを書く場所を見つける 3. 依存関係を排除する 4. テストを書く 5.
変更とリファクタリングを行う 9
レガシーコード改善に役立つ知識 10
引用・参考について前置き • この章の内容・ソースコードは下記本の内容を引用・参考にし て作成しています。 − 『Androidテスト全書』 − 著者: 白山 文彦,
外山 純生, 平田 敏之, 菊池 紘, 堀江 亮介 − https://peaks.cc/books/android_testing − 『レガシーコード改善ガイド』 − 著者: マイケル・C.フェザーズ 11
テストダブル • テスト対象が依存しているコンポーネントを本物そっくりに振る 舞う代役(ダブル)と差し替えることで、自分の期待する挙動や 値の返却を達成する • レガシーコード改善の中では、テスト対象が依存しているクラ ス・コントロールしたいクラスをテストダブルに置き換えながら テストを整備していく 12
テストダブル • 5つのパターン − スタブ − モック − スパイ −
フェイク − ダミー 13
スタブ • 事前に定義した任意の値をテスト対象に与える • 依存コンポーネントをスタブに置き換えることで、テスト対象に 任意の値を渡すことができる 14
スタブ実装例(テスト対象コード) public class WeatherForecast { private Sattelite satellite; boolean shouldBringUmbrella()
{ Weather weather = satellite.getWeather(); switch (weather) { case RAINY: return true; default: return false; } } } 15 satellite.getWeatherがRAINYのときのみ trueを返すメソッドをテストする
Libraryを用いたスタブの作成 Mockito/mockKともにモック・スパイオブジェクトがstubの機能を備えている Mockito(Java) Sattelite mock = mock(Sattelite.class); when(mock.getWeather()).thenReturn(Weather.RAINY); WeatherForecast testTarget
= new WeatherForecast(mock); boolean result = testTarget.shouldBringUmbrella(); assertThat(result).isEqualTo(true); 16 任意の値を返却する設定
スタブの機能を実装する open class Satellite { open fun getWeather(): Weather {
/* 元々の実装 */ } } /* 任意のWeatherを返すことができるスタブ */ class StubSatellite(val anyWeather: Weather) : Satellite() { override fun getWeather(): Weather { return anyWeather } } 17 任意の値を渡す 設定した任意の値を 返却
モック 18 • テスト対象が依存コンポーネントに与える値や挙動(出力)を検 証する
モック実装例(テスト対象コード) public class WeatherForecast { private WeatherRecorder recorder; void recordCurrentWeather(Weather
weather) { recorder.record(weather); } } 19 内部でWeatherRecorder#recordを呼び出し ているメソッドをテストする
Mockito(Java) WeatherRecorder mock = mock(WeatherRecorder.class); WeatherForecast testTarget = new WeatherForecast(mock);
testTarget.recordCurrentWeather(Weather.SUNNY); verify(mock).record(Weather.SUNNY); Libraryを用いたモックの作成 20 メソッドが呼び出されたかと 与えられた値の検証
モックの機能を実装する 21 open class WeatherRecorder { open fun record(weather: Weather)
{} } class MockWeatherRecorder : WeatherRecorder() { var weather: Weather? = null var isCalled = false override fun record(weather: Weather) { this.weather = weather isCalled = true } } メソッドの呼び出しと 与えられた値を記録
スパイ 22 • テスト対象が依存コンポーネントに与える値や挙動(出力)を記 録する • モックライブラリで提供されているスパイは、 実際にメソッドコールを行う振る舞いをすることが多い
スパイ実装例(テスト対象コード) public class WeatherForecast { private WeatherRecorder recorder; void recordCurrentWeather(Weather
weather) { recorder.record(weather); } } 23 モックの際と同様のメソッドをテスト
Libraryを用いたスパイの作成 Mockito(Java) WeatherRecorder spy = spy(new WeatherRecorder()); WeatherForecast testTarget =
new WeatherForecast(mock); testTarget.recordCurrentWeather(Weather.SUNNY); verify(mock).record(Weather.SUNNY); 24 クラスのインスタンスを 渡して生成する
スパイの機能を実装する 25 open class WeatherRecorder { open fun record(weather: Weather)
{} } class SpyWeatherRecorder : WeatherRecorder() { var weather: Weather? = null var isCalled = false override fun record(weather: Weather) { this.weather = weather isCalled = true super.record(weather) } } 実クラスのメソッドを 呼び出し
[補足] xUnit Test Patternsでの定義 • 現在のモックライブラリで提供されている機能とxUnit Test Patternsでの定義とは全く一緒ではない場合がある − テストダブルはx
Unit Test Patternsで定義された用語 − 参考リンク − https://www.amazon.co.jp/dp/B004X1D36K/ − http://xunitpatterns.com/Test%20Double.html − http://goyoki.hatenablog.com/entry/20120301/1330608789 26
フェイク・ダミー 27 • フェイク − 実際のコンポーネントと同等かそれに極めて近い挙動を持つ実装オブジェクト − 例: RoomのInMemoryDatabase •
ダミー − テストの結果に影響を与えないが、テスト対象クラスの生成・メソッドの呼び出 しに使用する代替オブジェクト
テストダブルの使いどころ 28 テストしたいクラス 依存しているクラス APIとの通信で実行時間がかかる など、いやな副作用がある 特定の振る舞いをテストしたいが、 再現するのが大変 依存しているクラスのメソッドが正し い引数で呼ばれているかわからな
い 参照
テストダブルの使いどころ 29 テストしたいクラス 依存しているクラス このクラスをテストダブルに 置き換えてテストする
テストダブルの使いどころ 30 テストしたいクラス 依存している クラスのテストダブル 特定の振る舞いを再現する値を返 すようにする メソッド呼び出し時の引数の値を記 録して検証できるようにする APIとの通信など副作用がある処
理を実行しない
レガシーコード改善テクニック 31
レガシーコードのジレンマ 32 • コードを変更するためには、テストを整備する必要がある。多く の場合、テストを整備するためには、コードを変更する必要が ある。 − レガシーコード改善ガイドでは、最低限のリファクタリングでテストを書けるよう にするテクニックを紹介している
テストの書きにくいコードが抱える課題 33 • テストしたいコードがAPI通信などテストコードで 実行したくない機能に依存している • テストしたいコードがグローバルなSingletonクラスに 依存している • テストしたいコードがprivateになっている
• ゴッドクラス/モンスターメソッド • Fat Activity/Fragment
コンストラクタのパラメータ化 34 • クラス内部でインスタンスの生成をしている際に、コンストラク タのパタメータとして外から渡すようにする • ユニットテストを書く際に不都合なDB・APIへのアクセスなどを テストダブルに差し替えることができる • テストしたいコードから依存を切り離すための基本的なアプ
ローチ
コンストラクタのパラメータ化 35 class ToDoItemRepository() { val api = ToDoItemApi() fun
save(toDoItem :ToDoItem) { api.save(toDoItem) } } @Test fun test() { val repository = ToDoItemRepository() save(ToDoItem("walking")) } APIへの送信が行われてしまう
コンストラクタのパラメータ化 36 class ToDoItemRepository(val api: ToDoItemApi) { fun save(toDoItem: ToDoItem)
{ api.save(toDoItem) } } @Test fun test() { val mockApi = mock(ToDoItemApi::class.java) val repository = ToDoItemRepository(mockApi) save(ToDoItem("walking")) verify(mockApi).save(ToDoItem("walking")) } モックオブジェクトをコンストラクタに渡すこ とでAPIの通信が行われない かつ与えた値をverifyで検証可能になる
静的setメソッドの導入 37 • Singletoneクラスにインスタンスを差し替えるsetterを作成し、 テストダブルを差し込めるようにする • テストしたいクラスがSingletonクラスを参照していて、テストを 書くのが難しい場合に有効 • setterには@VisibleForTestingアノテーションをつけてあげる
静的setメソッドの導入 38 class ToDoItemRepository(val api: ToDoItemApi) { fun save(toDoItem: ToDoItem)
{ // singletonなクラスへのアクセス Analytics.log("save todo item") api.save(toDoItem) } } @Test fun test() { val repository = ToDoItemRepository(mockApi) save(ToDoItem("walking")) verify(mockApi).save(ToDoItem("walking")) } Analyticsへの送信が行われてしまう
静的setメソッドの導入 39 class Analytics private constructor() { companion object {
private var instance: Analytics? = null fun getInstance(): Analytics { return instance } @VisibleForTesting fun setInstance(analytics: Analytics) { instance = analytics } } fun log(event: String) { } } Analyticsインスタンス差し替え setterを作成
静的setメソッドの導入 40 @Test fun test() { val mockAnalytics = mock(Analytics::class.java)
Analytics.setInstance(mockAnalytics) val repository = ToDoItemRepository(mockApi) save(ToDoItem("walking")) verify(mockApi).save(ToDoItem("walking")) } モックオブジェクトをインスタンスに セットすることでAnalyticsへの 送信が行われないようにする
@VisibleForTesting • 可視性をテスタビリティのために広く定義していることを示すア ノテーション • アノテーションがつけられたクラス・メソッド・フィールドをプロダ クトコードから呼び出すと、InspectionでWarningがでる 41
privateメソッドのテスト 42 • privateメソッドを新しいクラスとして抽出できないか? − クラスが多くのことを行いすぎていることが多い • publicメソッドを通じてテストが可能か? • 上記2つが難しい場合、メソッドに@VisibleForTestingアノテー
ションをそえた上で、package privateやinternalにする Kotlinのアクセス修飾子 同じモジュール内からアクセス可の意
テストのためにsetter作成や可視性をさげること 43 • 理想的な設計ではない − パラメータで渡せるようにしたり、クラスとして抽出するべき − 理想的な設計にするためには、大規模な改修になってしまうこともある • テストを整備しておけば、あとでコードをもっときれいすること
が可能 • @VisibleForTestingを修正時の目印にする
スプラウトメソッド・スプラウトクラス 44 • 要件を追加する際に新しいメソッド or クラスとして実装し、既 存のコードには新しいメソッド or クラスの呼び出しのみ追加す る
• 新しく追加したコードにはテストを作成する • 既存のコードにテストを書くのは諦める • ゴッドクラス・モンスターメソッド・FatActivty(Fragment)との戦 闘を一旦回避するために有効
スプラウトメソッド・スプラウトクラス 45 スプラウトクラス 依存が多くて インスタンスを 作るのが大変... すでにクラスが 大きくて 変更したくない...
リファクタリング 46 • リファクタリングはどのように進めていく? • 『リファクタリング』本の内容は現役で参考になる • 本の内容をチャートでまとめてくれているサイトもあるので、リ ファクタリング時に対象のクラスにどの方針をとればいいか確 認してみる
− http://objectclub.jp/technicaldoc/refactoring/refact-smell
IntelliJのリファクタリング機能 47 • レガシーコードの改善&リファクタリング時に、IntelliJのリファク タリング機能を使うことでより安全に作業ができる − https://pleiades.io/help/idea/refactoring-source-code.html • ショートカット例 −
⌃T : 使用できるリファクタリング機能の一覧表示 − option + ⌘ + M : メソッドとして切り出す(Extract Method) − option + ⌘ + P : パラメータとして切り出す(Extract Parameter) − クラスを選択するとコンストラクタのパラメータとして切り出される