SlideShare a Scribd company logo
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のテスト
@Override
protected 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
Adapter
Activity
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

More Related Content

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