個人的Androidアプリ開発環境を晒す

前職経験や、個人的な経験の元に最近固まってきた開発環境を晒します。

ちなみにこの開発体制は、
**「ある程度継続して開発を続けるAndroidアプリの開発環境。そして直接的に開発するメンバーが1人」**
という前提があります。ほぼ一発公開放置のアプリであれば自由に開発すればいいし、直接的に開発するメンバーが2人以上になった場合、「同じプロジェクトを同時に手を加えている場合」「メンバー同士のコミュニケーション」の部分を考える必要があるからです。

## はじめに

開発体制は文字通りユーザー体験に大きな影響を及ぼします。つまりキチンとUXを考えなければいけません。逆に対象が自分自身となるので難易度はとても易しいとも思います。
と考えるといろいろ夢が膨らみすぎてしまうので、いったん自分は以下のルールで開発環境を整えることに決めています。

- 新しい技術を導入するときに、環境が障害にならないようにする
- メンテナンスにかけるコストを減らす
- できればお金はあまり使いたくない（使えない）

というルールの上で「頭で考えない作業を自動化させて、開発に集中できる」環境を構築することにしました。

## 環境

|項目|内容|
|:--|:--|
|CI|Jenkins（Mac環境）|
|ソースコード管理|Bitbucket or GitHub|
|動作検証|DeployGate or Crashlytics（Beta）|
|検証端末|Android Emulator 2.0＋手持ちの端末いくつか|

各環境の選定理由は各々解説します。
ソースコード管理に関してですが、単にプライベートリポジトリ無制限という理由でBitbucketを採用していました。ですが最近の料金プラン変更もあり、GitHubへの完全移行を予定しています。

## 開発フロー
release/xxxを切らない、Gitフローの簡易版で行っています。

|ブランチ名|説明|
|:--|:--|
|master|常に最新リリース（Google Playへのアップロード）時点のソースコード|
|develop|開発時のメインとなるブランチ、ここからfeature/xxxを切っていく|
|feature/xxx|基本的に１機能１ブランチ|
|hotfix/xxx|リリース後に重大なバグがあったらmasterから切って作る|

直接開発するのが自分一人なので、リリースチェック時点にdevelopが動くことはありません。という理由で基本的にfeatureブランチだけで完結してしまいます。さらにAndroidは審査期間もないのでhotfixが必要になる状況もほとんどないですね。でも大改修中にリリースバージョンで重大な問題が見つかった場合も０ではないので、hotfixブランチの存在は必要だと思っています。

それとこれはこの後のCIにも関係してきますが、当然developも、プルリク時点のfeatureブランチやhotfixブランチはビルド＋テスト通過できている必要があります。

なお、「大改修」みたいなもののみ、`feature/xxx/aaa`や`feature/xxx/bbb`といった形のfeatureブランチを更にブランチで切ることを許可する特別ルールを（頭の中で）設けていますが、原則禁止にしています。なぜかといえば、そもそも開発体制が一人でプルリクのコードレビューが意味をなしていないので、それをするならテスト通過を重視すべきだからという理由です。

各ブランチからのマージは、全てプルリクによって行っています。これはCIのトリガーにも利用していることが理由です。

## CI環境について

### 選定理由

いろいろ考えた末に「Macで動かしているJenkins」に決定しました。
選択の対象にCircleCIがありましたが

- iOSアプリのCI環境としても共存させたい
	- CircleCIでiOSビルドする場合は有料（というかプランがいまいち不明）
		- 一番安そうな$39/moでも、2年使い続けたらmac mini代ぐらいになるし…
- 開発は最新環境で進めたい
	- CircleCIはちょっと遅いっぽい？（特にAVD周り）
	- 自宅のmacなら、自分のタイミングでSDKを更新することができる（開発機と同期がとれる）

といった理由で「制限が少なく」「Android SDKの最新環境がすぐに導入できる」Jenkinsに落ち着きました。

とはいえ、Jenkinsは気をつけないと簡単に秘伝のタレみたいなものが出来上がってしまいます。
なのでシェルで行う動作はプロジェクト内に`jenkins.sh`というシェルを用意して叩くだけで済むようにして、これはJenkinsでなくても利用可能なものを作るようにしています。

```sh:jenkins.sh
#!/bin/bash -e

PROGNAME=$(basename $0)

usage() {
    echo "Usage: $PROGNAME [OPTIONS] WORKSPACE"
    echo "Options:"
    echo "  --help : help"
    echo "  --dev      : dev build and beta upload"
    echo "  --release  : release build and beta upload"
    echo " "
    echo " "
    exit 1
}
shell_session_update() { :; }

for OPT in "$@"
do
    case "$OPT" in
        '--help' )
            usage
            exit 1
            ;;
        '--dev')
            BETA_DEV=true
            ;;
        '--release')
            BETA_RELEASE=true
            ;;
        -*)
            ;;
        *)
            WORKSPACE_PATH=$OPT
            shift 1
            ;;
    esac
done

if [ ! "$WORKSPACE_PATH" ]; then
    echo "No Set WORKSPACE_PATH"
    usage
    exit 1
fi

if [ ! "$BETA_DEV" ] && [ ! "$BETA_RELEASE" ]; then
    echo "No Set Options"
    usage
    exit 1
fi

echo "build start"
export LANG=en_US.UTF-8
cd $WORKSPACE_PATH

if [ "$BETA_DEV" ]; then
    #./gradlew clean check cAT build uploadDeployGateRelease
    ./gradlew clean check build uploadDeployGateRelease
fi

if [ "$BETA_RELEASE" ]; then
    ./gradlew clean check build publishApkRelease
fi

echo "build end"
```

![スクリーンショット 2016-06-08 14.34.21.png](https://qiita-image-store.s3.amazonaws.com/0/4560/af722233-9a2c-0d5b-8484-568ff03be2e9.png "スクリーンショット 2016-06-08 14.34.21.png")

シェルの内容は気にしてはいけません（素人なので）。

### ジョブの設定内容

Jenkins内で用意しているジョブは

- featureからdevelopへのプルリク時にテスト＋DeployGate（or CrashlyticsBeta）へアップロード
- masterが更新されたタイミングで、Google Playへアルファ版としてアップロード

の2種類のみを設定しています。hotfix＞masterは現在利用する状況がほぼ０なので用意していません（実際にはあるべきです）。

### ベータアプリ配信サービスについて

現状ではEarly対象になっていることもあって、制限が非常にゆるいDeployGateを使っていますが、現行のDeployGateは「プロフィールページ」によって、（無料プランの場合）アプリの更新や新規アプリのアップデートが公開されてしまいます。
そこからダウンロードはできないようですが、心理的に「公開までステータスを隠したい」人にはかなり精神的な敷居があると思います（なんでこんなことしちゃったんだろう……）。また、Early対象になっているとどこまで制限がゆるいのかという解説がどこにもないのも個人的には不安要素の一つになっています。

そういった意味で、全部英語表記であることに抵抗がなければ、Crashlytics Betaを利用した配信をオススメします。

### Google Playへのアルファ版アップロードについて

リリース前レポートで、ざっと最終チェックをしたいからです。
リリース前レポートの詳細は、別途Qiitaでまとめました。

[Google Play Developer Consoleの「リリース前レポート」を試す - Qiita](http://qiita.com/yamacraft/items/2effc86401af3f70248b)


## テスト環境について

テストについてですが、Androidに関しては

- Local Unit TestはJUnit3＋mockito
- Instrumented Unit TestはJUnit4＋Espresso

で行っています。要はこれ、公式で推奨されているテスト環境です。

[Testing UI for a Single App | Android Developers](https://developer.android.com/training/testing/ui-testing/espresso-testing.html)

理由はただひとつ「（特にUIテストは）API更新時に動かなくなる＋最新OSでテストできなくなる」という問題を回避するためです。
Robolectricが一時期良く名前に上がりましたが、こちらはAppCompat導入時など最新Widgetの導入時にテストがこけやすく、最新のAPIを導入する際の精神的な大きな敷居になることが多くあります。
なので、せめて公式提供（＋推奨）されているものを優先して利用することにしています。

もしこれでもAPIバージョンアップ時にメンテコストが大きくかかったり、これによってテストが動かなくなった場合は容赦なくテストの方を切り捨てます（そして手作業でのテストの比重を増やす）。
これは当初のルールで安定より取り込みたい技術を優先しているから行えることでもあります。

## 検証端末について

UIテストも含め、開発全般でEmulatorを使っています。

シェアが高い端末をもっているわけではないし、中途半端な実機でチェックするぐらいならエミュで複数OSの動作をチェックした方がいいし、なにより2.0になってからGenymotionを使うメリットがないぐらい、動作も早く安定するようになったからです（感想には個人差があります）。

正直、個々の端末特有の動作を事前にカバーするのは無理です。そして起きたとしても、手元に端末がなければ対応することも無理です。
とりあえずCrashlyticsなどのクラッシュレポートを収集するツールは導入しておき、可能なら対応するレベルの意識でいます。

## 現状の問題点

### CIでcATするときに、事前にエミュレータを立ち上げておく必要がある

いろいろ調べてみたんですが、通常の`emulator`コマンドではエミュを閉じるまでフォアグラウンドが生き続けてしまうので、事前に必要なエミュレータを立ち上げたり、必要な端末をmacに繋いでおく必要があります。
できればここは「テスト前に起動させて、終了したら閉じる」をやっておいて、事前のjenkins（が動いているmac）の状況を気にせずにやりたいなあという次第です。

### 個人macなのでwebhookが使えない

固定IPじゃないので、知らず切り替わったときにいちいち更新させないといけない等々の理由でポーリングによるトリガーになっています。なのでどうしても動作に数分レベルのラグが起きるのが難点です。
あと最近Jenkins2.0になったせいか、GitHub PullRequest Builderの動作が不安定な気がします（なぜか１プルリクで３つぐらいビルドが立ち上がるので）。

## 全体図

こんな感じです。

![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/4560/bab6d649-3d11-5d2d-3e12-4f54e22694c0.png "Untitled.png")


以上です。