52
DroidKaigi 2015/04/25 @cattaka_net 開発を効率的に 進めるられるまでの道程 Takao Sumitomo @cattaka_net

開発を効率的に進めるられるまでの道程

Embed Size (px)

Citation preview

DroidKaigi 2015/04/25 @cattaka_net

開発を効率的に

進めるられるまでの道程

Takao Sumitomo@cattaka_net

DroidKaigi 2015/04/25 @cattaka_net

自己紹介● 住友 孝郎(Takao Sumitomo)● Androidアプリ開発者● 開発経歴

● Androidアプリ● iOSアプリ(ちょっとだけ)● 業務系Webアプリケーション● 業務系Windowsアプリ

● その他● 電子工作● OpenCV

● ウォンテッドリー株式会社所属

2014年12月〜

DroidKaigi 2015/04/25 @cattaka_net

Androidアプリ開発の経歴● HT-03Aの頃(2009年くらい)からAndroidアプリ

作ってます。● 当時は受託と派遣を主にやってました。● 手に負えなくなったアプリの改修をよくやってました

DroidKaigi 2015/04/25 @cattaka_net

プロの力が身につくAndroidプログラミングの教科書

DroidKaigi 2015/04/25 @cattaka_net

駄目コードについて、C87で書きました

技術サークル

TechBooster

DroidKaigi 2015/04/25 @cattaka_net

トピック● アプリ開発の効率化について● テストを書くに至った経緯● 開発途中からテストを導入する話

DroidKaigi 2015/04/25 @cattaka_net

アプリ開発の効率化について

DroidKaigi 2015/04/25 @cattaka_net

ボトルネックの潜む場所

● 開発で行うこと● プログラムを書く

● ソースコード管理

● テスト

● テスト版アプリの配布

● これらをどこまで自動化するか?

DroidKaigi 2015/04/25 @cattaka_net

ボトルネックの対策● 開発で行うこと

● プログラムを書く– 諦めて書く。良いライブラリを使う。良いIDEを使う。

● ソースコード管理– GitやSVNを使う。Git FlowやGitHub Flowを使う。

● テスト– JUnitを使う。– JenkinsやTravisCIやCircleCIで自動化する。

● テスト版アプリの配布– DeployGate等を使う。

ここが一番ネックになる

DroidKaigi 2015/04/25 @cattaka_net

なのでテストの話をします

DroidKaigi 2015/04/25 @cattaka_net

テストを書くに至った経緯

DroidKaigi 2015/04/25 @cattaka_net

開発してるとよくある話

● 「バグゼロで」● 「品質は100%で」

DroidKaigi 2015/04/25 @cattaka_net

どうしてたか?

● エクセルのテストシート(山盛り)● エビデンス(という名のスクリーンショット)を取る● 全機種で実機テスト● 仕様変更があったとき

→全部再テストしてくださいね^^

ツライ!(゚Д゚)ツライ!(゚Д゚)

DroidKaigi 2015/04/25 @cattaka_net

よくある問題● 小さなリファクタリング

● 意識してないところで壊れる、、orz● リファクタリングしないようにする

● ハウルの動く城みたいになる● 書きたいように書けなくなるので更にストレスが貯まる

ツライ!(゚Д゚)

DroidKaigi 2015/04/25 @cattaka_net

テストを書き始める

● 最初はロジックのテストから書く● 要件一覧表の1項目1テストを書くこともやった

● 実は個人的に一番効果があった(効率悪いけど)● 「壊す恐怖」から解放された

● 以降、設計レベルからテストを書くようにした

DroidKaigi 2015/04/25 @cattaka_net

開発途中からテストを導入する話

DroidKaigi 2015/04/25 @cattaka_net

普通のテスト要求分析

基本設計

機能設計

詳細設計

コーディング

受け入れテスト

システムテスト

結合テスト

単体テスト

DroidKaigi 2015/04/25 @cattaka_net

普通のテスト要求分析

基本設計

機能設計

詳細設計

コーディング

受け入れテスト

システムテスト

結合テスト

単体テスト

通常はテストも順番に作る。後から作るのは大変。

DroidKaigi 2015/04/25 @cattaka_net

アプローチ

● 外部依存する箇所にDI(オレオレ可)を入れる● 通信処理● データベース● プリファレンス

● グローバル変数を取る(static変数)● シングルトンという名のグローバル変数も取る● 投げっぱなしのスレッドを止める● 最小限のリファクタリング● ひたすらテストを書く

DroidKaigi 2015/04/25 @cattaka_net

ひたすらテストを書く

DroidKaigi 2015/04/25 @cattaka_net

具体的な修正1● 修正前

アプリケーション

通信処理データベース

SharedPreferences

DroidKaigi 2015/04/25 @cattaka_net

具体的な修正2

● 修正後

アプリケーション

通信処理データベース

SharedPreferences

DroidKaigi 2015/04/25 @cattaka_net

具体的な修正3

● テスト時の構成

アプリケーション

通信処理のモックデータベースのモック

SharedPreferencesのモック

DroidKaigi 2015/04/25 @cattaka_net

具体的な修正3

● テスト時の構成

アプリケーション

通信処理のモックデータベースのモック

SharedPreferencesのモック

要はこれらをモックに差し替えられればテストが書ける

DroidKaigi 2015/04/25 @cattaka_net

どうやって差し替えるか● DaggerなどのDIライブラリを使う● オレオレDIを使う

オレオレDIでも無いより良い

DroidKaigi 2015/04/25 @cattaka_net

SharedPreferencesの差し替え● Context#getSharedPreferencesを付ける● 呼ぶときの引数にPrefix等を付ける● 直接↑を呼ぶのではなく、Factoryクラスを作る

DroidKaigi 2015/04/25 @cattaka_net

SharedPreferencesの差し替えプロダクションコードpublic class SharedPreferencesFactory { static SharedPreferencesFactory INSTANCE = new SharedPreferencesFactory();

public static SharedPreferencesFactory getInstance() { return INSTANCE; }

public SharedPreferences newInstance(Context context, String name) { return context.getSharedPreferences(name, Context.MODE_PRIVATE); }}

テスト用のダミーpublic class DummySharedPreferencesFactory extends SharedPreferencesFactory { public SharedPreferences newInstance(Context context, String name) { SharedPreferences pref = context.getSharedPreferences( "test_" + name, Context.MODE_PRIVATE); pref.clear(); return pref; }}

テストのときはここをダミーに差し替える

DroidKaigi 2015/04/25 @cattaka_net

SQLiteOpenHelperの差し替え● RenamingDelegatingContextを使えば

一時的に別のDBファイルにできる● SQLiteOpenHelperにname=nullを渡すと

オンメモリのデータベースが作れる

どっちでもテストは書ける

DroidKaigi 2015/04/25 @cattaka_net

SqlteOpenHelperの差し替えプロダクションコードpublic class OpenHelperFactory { static OpenHelperFactory INSTANCE = new OpenHelperFactory();

public static OpenHelperFactory getInstance() { return INSTANCE; }

@Override public OpenHelper createOpenHelper(Context context) { return new OpenHelper(context); }}

テスト用のダミーpublic class DummyOpenHelperFactory extends OpenHelperFactory { public OpenHelper createOpenHelper(Context context) { Context c = new RenamingDelegatingContext(context, "test_"); return new OpenHelper(c); }}

テストのときはここをダミーに差し替える

DroidKaigi 2015/04/25 @cattaka_net

通信処理の差し替え● 予め通信処理は1つにまとめておく● オレオレDIで通信処理を差し替える● 偽の通信データを返すようにする● 偽の通信データは

androidTest下のassetsに入れる

DroidKaigi 2015/04/25 @cattaka_net

ひたすらテストを書く

● ブラックボックステスト● ウォークスルー

● コンバージョンに繋がるところは重点的に書く● ログイン周り● 応募● ユーザーのプロフィール入力

● テストの粒度はマチマチ

DroidKaigi 2015/04/25 @cattaka_net

ひたすらテストを書く

DroidKaigi 2015/04/25 @cattaka_net

悲しいこともあるけど、、

DroidKaigi 2015/04/25 @cattaka_net

具体的な例

DroidKaigi 2015/04/25 @cattaka_net

たとえばリスト画面

● この画面に関連する部品● Activity● Adapter● ListView● Database

● どのテストを書く?

Activity

ListView

Database

Adapter

DroidKaigi 2015/04/25 @cattaka_net

Adapterのテスト● Adapter単独で考える● Contextとダミーのデータを与える● getViewで生成されたViewを確認する● InstrumentationTestCaseが使える

Activity

ListView

Database

Adapter

Context

Adapter

DummyData

生成されたView

getViewメソッド

これらが対応しているかのテストを書く

テストのときは切り離して考える

DroidKaigi 2015/04/25 @cattaka_net

Adapterのテストpublic void testGetView() { List<CheckListItem> dummys = new ArrayList<>(); { // ダミーデータを作る dummys.add(new CheckListItem(1L, 1L, 1L, "Label1")); dummys.add(new CheckListItem(2L, 2L, 2L, "Label2")); }

Context context = getInstrumentation().getTargetContext(); MyAdapter sup = new MyAdapter(context, dummys);

{ // 1つめのViewの表示内容を確認する View view = sup.getView(0, null, null); assertThat(view, is(Matchers.instanceOf(CheckedTextView.class))); assertThat(((CheckedTextView)view).getText().toString(), is("Label1")); } { // 2つめのViewの表示内容を確認する View view = sup.getView(1, null, null); assertThat(view, is(Matchers.instanceOf(CheckedTextView.class))); assertThat(((CheckedTextView)view).getText().toString(), is("Label2")); }}

DroidKaigi 2015/04/25 @cattaka_net

Databaseのテスト● CRUD系はテストを書く● RenamingDelegateContextが便利● InstrumentationTestCaseが使える

Activity

ListView

Database

Adapter

テストのときは切り離して考える

Database

DummyData1

DummyData2

insert

select

これらが対応しているかのテストを書く

DroidKaigi 2015/04/25 @cattaka_net

Databaseのテスト@Overrideprotected void setUp() throws Exception { super.setUp(); Context context = new RenamingDelegatingContext( getInstrumentation().getTargetContext(), "test_"); mOpenHelper = new OpenHelper(context);}

public void testInsertSelect() { CheckListEntry orig = new CheckListEntry(); orig.setTitle("hoge");

{ // INSERTする mOpenHelper.registerEntry(orig); } CheckListEntry dest; { // SELECTする Long id = orig.getId(); dest = mOpenHelper.findEntry(id, false); } { // 確認する assertThat(dest.getTitle(), is("hoge")); }}

DroidKaigi 2015/04/25 @cattaka_net

Activity● 外部依存やストレージのみダミーに置き換える● ActivityInstrumentationTestCase2が使える● それぞれの部品の疎通確認程度に留める

Activity

ListView

Database

AdapterActivity

ListView

DummyDatabase

Adapter

Databaseのみダミーに置き換えて考える

これらが対応しているかのテストを書く

DroidKaigi 2015/04/25 @cattaka_net

ツールやライブラリ

● 基本的に一般的なものを使用● JUnit4● ./gradlew connectedAndroidTest● ./gradlew createCoverageReport

● Espresso (android-test-kit)● UI周りのテストが簡潔に書ける

● Mockito● モックが簡単に作れる

● Crashlytics

./gradlew create(Debug/Release)CoverageReport

DroidKaigi 2015/04/25 @cattaka_net

connectedAndroidTestのレポート

DroidKaigi 2015/04/25 @cattaka_net

createCoverageReportのレポート

DroidKaigi 2015/04/25 @cattaka_net

標準でも十分強力

DroidKaigi 2015/04/25 @cattaka_net

まとめ

DroidKaigi 2015/04/25 @cattaka_net

いきなり効率化は無理

DroidKaigi 2015/04/25 @cattaka_net

少しずつ自動化しよう

DroidKaigi 2015/04/25 @cattaka_net

まずはテストを書くところから始めよう

DroidKaigi 2015/04/25 @cattaka_net

ボトルネックの対策● 開発で行うこと

● プログラムを書く– 諦めて書く

● ソースコード管理– GitやSVNを使う。GitFlowやGitHubFlowを使う。

● テスト– JUnitを使う– JenkinsやTravisCIやCircleCIを使う

● テスト版アプリの配布– DeployGateやFabricを使う

ここが解決すれば後の自動化もできる

DroidKaigi 2015/04/25 @cattaka_net

サンプルアプリ● FastCheckList● テストが直ぐに実行できるようになっています

DroidKaigi 2015/04/25 @cattaka_net

貴方がテストを書けば効率は上げられる

DroidKaigi 2015/04/25 @cattaka_net

ご清聴ありがとうございました

Takao Sumitomo@cattaka_net