AndroidアプリでBitrise+Danger+Jacoco+CodecovのCI環境セットアップ

# お品書き

- AndroidアプリのCI環境をBitriseで構築
- DangerでPull Request時のLintチェック
- Jacoco+Codecovでテストのカバレッジ可視化
- ビルド完了はSlackに通知

# 環境

- macOS Mojave 10.14.5
- Android Studio 3.4.1

# AndroidアプリのCI環境をBitriseで構築

とりあえずデフォルトプロジェクトをEmpty Activityで作成してユニットテスト用に簡単なクラスを用意します。
今回はLocal Unit Testのみです。
ソースコードはGitHubに上げていますので、詳細はそちらをご参照ください。

[android-bitrise-sample](https://github.com/sadashi-ota/android-bitrise-sample)

これをBitriseに追加していきます。

- Bitriseのアカウント未作成の場合
    - アカウント登録後、「Add First App」から追加をします
- Bitriseのアカウント作成済みの場合
    - [Bitriseのダッシュボード](https://app.bitrise.io/dashboard/builds)を開き、下図の右上にある「Add New App」ボタンを押します。

あとは、アカウント、サービス、リポジトリなどを順に設定していくと、プロジェクトのプラットフォームを自動で判別してくれます。
とりあえず、Project build configurationなどは、普通に作成していればそのままで問題ありません。
今回のサンプルではvariantも特に指定しません。

WebHookについてはPull Requestをトリガーに自動でビルドしたいので登録しておきましょう。
全て完了したら、「We've kicked of your first test build for you!」とあるのでそこをクリックしてビルド画面に移動します。
最初は「primary」というワークフローにRepository clone, build, lint, unit testなど一通り実行される設定がされています。
このビルドが通るのが確認できたら基本設定は完了です。

- [参考ビルドの結果](https://app.bitrise.io/build/f651e51885b6a692#)

# Dangerでプルリク時のLintチェック

大まかな手順としては以下の通りです。

1. ローカルでDangerfileを作成する
2. GitHubでPersonal access tokenを作成する
3. Bitriseで作成したAccess tokenを設定する
4. BitriseでDangerを動かすWorkflowを追加する
5. トリガーを変更する

以下、それぞれの手順です。

## 1. ローカルでDangerfileを作成する

`Bundler`を利用する場合と利用しない場合の２種類のセットアップがあります
どちらかの方法で`Dangerfile`を作成後、以下のサンプルのように`Dangerfile`を編集してコミットしてください。

- [Dangerfileのサンプル](https://github.com/sadashi-ota/android-bitrise-sample/blob/master/Dangerfile)
    - 「WIP」ラベルが付与されているか
    - 500行を超えるプルリクか
    - android_lintは問題ないか

### `Bundler`を利用しない場合

```shell
gem install danger
danger init
```

### `Bundler`を利用する場合

```shell
gem install bundler
cd {プロジェクトルート}
bundle init
echo "gem \"danger\"" >> Gemfile
echo "gem \"danger-android_lint\"" >> Gemfile
bundle install --path vendor/bundle
echo "
# vendor
vendor/
" >> .gitignore
bundle exec danger init
```

`{プロジェクトルート}`の部分はAndroidプロジェクトのルートディレクトリです。
vender/bundle配下はGitにコミットしたくないので、.gitignoreにパスを追加します。

#### Bundlerを使う場合の注意点

個人的には`Bundler`を利用する方が好みですが、その場合の注意点としてはBitrise上の`Bundler`は`1.x`に対して、今ローカルでインストールすると`2.x`がインストールされます。
（2019/07/01現在ではBitriseが1.17.3で、最新が2.0.2です）
`2.x`で作った`Gemfile.lock`でBitriseで動かすと以下のようにエラーになります。

```
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and
installing your bundle as root will break this application for all non-root
users on this machine.
You must use Bundler 2 or greater with this lockfile.
```

この場合の対処は以下があります

- `Bundler`で管理するのをやめる
- ローカルの`Bundler`もBitriseと同じ`1.17.3`にする
- ローカルにもBitriseと同じバージョンの`Bundler`をインストールして、`bundle _1.17.3_ install --path vendor/bundle`のようにバージョン指定して使う
    - 複数バージョンの`Bundler`を扱うには以下の記事が参考になります
        - [Ruby | bundler を特定のバージョンに切り替えて実行する](https://qiita.com/YumaInaura/items/64e5721549e4927ce85f)

なお、後述するDangerを実行するStepで「Run Danger」を利用する場合は`Bundler`での管理は必須です。
（`Gemfile`をコミットしないと動きません）

## 2. GitHubでPersonal access tokenを作成する

1. GitHubのアカウント設定のPersonal access tokensの画面を開く
    - Settings > Developer settings > Personal access tokens
2. 「Generate new token」からtokenを作成する
    - scopeはrepoのみフル権限あれば十分です
3. 生成されたtokenを控えておく
    - わからなくなった場合はtokenを作成しなおす必要があります

## 3. Bitriseで作成したAccess tokenを設定する

1. 作成したアプリケーションをDashbordから選択し、Workflowのタブを選択する
2. 「Secrets」のタブを選択する
    - 「Secrets」に登録する情報は、BitriseのBuildコンソール上からも表示されません。
    - Access Tokenなど隠しておきたいものはここに登録すると良いです
3. 「Add New」ボタンから追加する
    - Key : DANGER_GITHUB_API_TOKEN
    - Value : 控えたおいたGitHubのAccess token
    - 「Expose for Pull Request」はONにする
        - 結構これを忘れがちです
        - これをONにしないとPull Requestからこの環境変数が参照できず、Dangerが失敗します
    <img width="609" alt="Expose_for_Pull_Request.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189865/cf44b064-614e-df9a-5d64-c92c7c4eb115.png">
4. Saveボタンを押下する

## 4. BitriseでDangerを動かすWorkflowを追加する

まずはDanger実行用のWorkflowを作成します

1. 作成したアプリケーションをDashbordから選択し、Workflowのタブを選択する
2. 「+ Workflow」からDanger用のWorkFlowを作成する
    - 名前は任意で大丈夫です
    - BASED ON を「primary」にします
3. Dangerを実行するStepを追加する

BitriseでDangerを動かすには以下の２種類の方法があります。（他にもあるかもしれませんが）
どちらかのStepを「Android Unit Test」Stepの後に追加しましょう。

### `Run Danger`Stepを使う 

BitriseではDanger用のStepがあります。

- [GitHub - steps-danger](https://github.com/bitrise-steplib/steps-danger)

こちらで実行する場合は`Bundler`での管理が必要になります。

また、設定項目としては以下の通り、GitHubのアクセストークン設定をすればOKです

1. GitHubの項目をクリックして設定項目を開き、「Access token for your project」の項目をクリックするとさらに詳細が表示されます
2. 「Select secret variable」を選択して、登録してあるGitHubのAccess tokenを選択する

### `Script`Stepを使う

こちらは普通にScriptを実行するStepです。`Bundler`を使うか使わないかで以下のように設定してください。
ただし、danger実行時に参照されるリポジトリのURLがSSHのものが設定されている必要があるので、環境変数`GIT_REPOSITORY_URL`にSSHのパスを設定する必要があります。
以下の例は、サンプルプロジェクトの場合の設定です。

#### `Bundler`を利用しない場合

```shell
export GIT_REPOSITORY_URL=git@github.com:sadashi-ota/android-bitrise-sample.git
gem install danger
gem install danger-android_lint
danger
```

#### `Bundler`を利用する場合

```shell
export GIT_REPOSITORY_URL=git@github.com:sadashi-ota/android-bitrise-sample.git
bundle install
bundle exec danger
```

## 5. トリガーを変更する

1. 作成したアプリケーションをDashbordから選択し、Workflowのタブを選択する
2. 「Triggers」のタブを選択する
3. 「PULL REQUEST」で実行するWorkflowをDangerを設定したWorkflowに変更する
4. 右上の「⌘＋S」ボタンを押す

ついでに、「PUSH」で実行されるbranchも`master`のみに変更しておいても良いと思います。
（プルリク作るときにPUSHでもHookされてビルドが走るので）

これで`Danger`の設定は完了です。

## 実行結果

LintのチェックでエラーがあるとDangerが[**こんな感じ**](https://github.com/sadashi-ota/android-bitrise-sample/pull/1#discussion_r298816600)でコメントしてくれます。

# Jacoco+Codecovでテストのカバレッジ可視化

次にJacocoを導入してユニットテストのカバレッジを可視化していきます。

## Jacoco導入

Jacoco導入の設定例です

```diff:build.gradle
@@ -10,8 +10,7 @@ buildscript {
     dependencies {
         classpath 'com.android.tools.build:gradle:3.4.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        // NOTE: Do not place your application dependencies here; they belong
-        // in the individual module build.gradle files
+        classpath "org.jacoco:org.jacoco.core:0.8.4"
     }
 }
```

```diff:app/build.gradle
@@ -1,6 +1,11 @@
 apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
 apply plugin: 'kotlin-android-extensions'
+apply plugin: 'jacoco'
+
+jacoco {
+    toolVersion = "0.8.4"
+}
 
 android {
     compileSdkVersion 28
@@ -13,6 +18,9 @@ android {
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
     buildTypes {
+        debug {
+            testCoverageEnabled true
+        }
         release {
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@@ -33,3 +41,23 @@ dependencies {
     androidTestImplementation 'androidx.test:runner:1.2.0'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
 }
+
+task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
+    group = "Reporting"
+    description = "Generate Jacoco coverage reports after running tests."
+    reports {
+        xml.enabled = true
+        html.enabled = true
+    }
+
+    def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
+    def debugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
+    def mainSrc = "$project.projectDir/src/main/java"
+
+    sourceDirectories = files([mainSrc])
+    classDirectories = files([debugTree])
+    executionData = fileTree(dir: project.buildDir, includes: [
+            'jacoco/testDebugUnitTest.exec',
+            'outputs/code-coverage/connected/*coverage.ec'
+    ])
+}
```

これで`jacocoTestReport`というタスクを実行するとhtmlとxmlが出力されます。
おそらく出力先は`app/build/reports/jacoco/jacocoTestReport/`配下になっていると思います。

<img width="1110" alt="code_coverage.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189865/5da5c2db-c02a-e407-4254-91c06eb622be.png">
<img width="520" alt="code_coverage2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189865/327aa9d8-2921-58e6-7c94-c04ae06f4a63.png">


## Codecovの設定

### Codecovの登録

https://codecov.io/gh

「Add new repository」からリポジトリを登録します
Repository Upload Tokenが発行されますので、それをBitriseで設定します。

### Bitriseの設定

「Android Unit Test」Stepの後に以下のStepを追加します

1. jacocoのレポート作成Stepを追加する
    - 「Script」のStepを選択し、`./gradlew :app:jacocoTestReport`を実行する
2. その後に「Codecov」のStepを追加する
    - Codecovで発行したtokenを`Input variables` > `Codecov.io token`に登録します
    - Keyは任意で大丈夫ですが、今回は`CODECOV_TOKEN`に設定します

これを`primary`とDangerを設定したWorkflowに追加すれば完了です。

サンプルのリポジトリではわざとカバレッジが足りないようにしてありますので、一度primaryのWorkflowでビルドします。
完了後、Codecovに現在のカバレッジ等が表示されるようになります。

この状態からブランチを作成して、カバレッジが増えるような修正をしてみましょう。
そのプルリクが[**こちら**](https://github.com/sadashi-ota/android-bitrise-sample/pull/3)です。

マージするbaseブランチでCodecovが実行済みだと、compareブランチとの差分が表示されます。

もちろんテストのカバレッジはCodecovからでも詳細にみれます。

- [Codecovサンプル](https://codecov.io/gh/sadashi-ota/android-bitrise-sample/src/master/app/src/main/java/com/sadashi/bitrise/sample/Sample.kt)

以上でJacocoおよびCodecovの設定も完了です！

# ビルド完了はSlackに通知

これは以下のブログで紹介されていました

- https://blog.bitrise.io/slack-away

手順は以下の通りです

1. Slackの「アプリを追加する」から「Incoming Webhook」を選ぶ
2. Slackの設定を整える
    - 通知先のチャンネルとか、アイコンとか
3. 設定画面の「WebHook URL」をコピーする
4. Bitriseで「Send a Slack message」のStepをWorkflowの最後に追加する
5. 「Slack WebHook URL」に先ほどコピーしたURLを設定する
    - 環境変数は任意で大丈夫です
6. 「Target Slack channel, group or username」で通知するチャンネルを設定する
7. ビルドが失敗した時の通知は「If Build Failed」の項目を開いて「Target Slack channel, group or username」を設定する

デフォルト設定だと結果はこんな形で表示されます

<img width="501" alt="slack.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189865/429c025e-7e23-1e1b-ef08-58dfbb4020f8.png">

以上でSlackの通知も完了です。

# あとがき

いかがでしたでしょうか？
BitriseはGUIで設定ができるので、簡単にWorkflowが作れるのはメリットですね。
初期構築で少しつまづきやすいポイントも紹介してみましたので、初めて触る方のお役に立てば幸いです。
また、CI環境の構築に慣れている方は、「こんなStepもあったら楽なのに」って思うようなものがあれば、コントリビュートしてみるのも面白いかもしれませんね。