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
Androidテストハンズオン: テストのないアプリにテストを書こう編 / handson-w...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
tkmnzm
April 11, 2019
Programming
4.6k
1
Share
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.6k
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
690
コルーチンのエラーをテストするための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.5k
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
630
25分で作るAndroid Lint / Android Lint made in 25 minutes
tkmnzm
0
1k
2年半ぶりのプロダクト開発であらためて感じた自動テストの大切さ / realized the importance of automatic testing with product development for the first time in two and a half years
tkmnzm
1
870
Other Decks in Programming
See All in Programming
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
17
5.5k
柔軟なPDFレイアウトエディタを支える型システム設計 — Discriminated UnionとConditional Typeの実践
minako__ph
4
1.3k
CLIであることを活かしたGitHub Copilot CLI活用術 / GitHub Copilot CLI Pro Tips & Tricks
nao_mk2
1
1.2k
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.1k
TSKaigi 2026 TypeScriptバックエンドのオブザーバビリティ戦略 — Datadog × NestJSの実践
taiseiyamamotoan
2
260
AI駆動開発勉強会 広島支部 第一回勉強会 AI駆動開発概要とワークショップ
hayatoshimiu
0
430
技術記事、AIに書かせるか、自分で書くか? 〜それでも私が自分の手で書く理由〜 / #QiitaConference
jnchito
2
1.2k
自動レビューエンジンの実装と運用 ~レビューのない世界へ~
kurukuru1999
2
310
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
2.2k
Transactional Change Stream Processing With Debezium and Apache Flink
gunnarmorling
1
160
oxlintはeslint/typescript-eslintを置き換えられるのか
shomafujita
2
310
Swiftのレキシカルスコープ管理
kntkymt
0
210
Featured
See All Featured
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
590
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
260
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
65
55k
Typedesign – Prime Four
hannesfritz
42
3.1k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
570
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
55k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
320
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.7k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
860
Building Applications with DynamoDB
mza
96
7.1k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
120
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) − クラスを選択するとコンストラクタのパラメータとして切り出される