iOSアプリ開発におけるCI/CD

皆さん、iOSアプリ開発でCI/CDをどこまでやっていますか？
せっかくなので、最近やっていることについてまとめてみました。

最近では、CircleCIなどのクラウドCIを使うという手もありますが、細かいことをやりたい場合だとオンプレミスであるほうが何かと便利なため、Jenkinsを利用しています。

本来であれば、設定方法や工夫している点など色々と書きたいところですが、記事が長くなりすぎてしまうためあまり細かく書いていません。
それぞれのフェーズについて別途記事を書きたいと思います。

# 前提
今回の話として、対象となるアプリはアプリ単体で完結するものではなくサーバとやりとりします。

従って、以下のことも考慮しています。

 - アプリは(基本的に)世に出ている全てのバージョンをサポートする必要がある。
 - サーバは常に最新である。

つまり、全バージョンのアプリに対して最新のサーバで動くことを担保する必要があります。
また、サーバ環境については以下のとおりです。

## サーバ環境について
大まかにいうと「開発用」「検証用」「本番用」に分かれています。

用途としては以下のとおりです。

 - 開発用
   - 開発者が新規機能やバグ修正などをおこない、動作確認をする環境
 - 検証用
   - 開発用の環境での動作確認が問題ないと判断され、リリースするために検証をおこなうための環境
 - 本番用
   - マーケットにリリースされたアプリが接続する先であり、一般ユーザが利用する環境


# iOSアプリ開発においてやること
iOSアプリ開発の場合は以下の様なことが必要になります。

 1. 環境用意(Macのセットアップ周り)
   1. 環境構築
   - iOSアプリ開発用の証明書インストール
   - Apple Dev Centerへの登録
     - デバイスの追加
     - Provisioning Profileの更新
   - Provisioning Profileの設置
 - 開発/検証
   1. 実装(サーバ/アプリ)
   - サーバ側コードの単体テストの実行
   - サーバのデプロイ
   - アプリの自動テストの実行
   - アプリのビルド
   - マニュアルテスト
 - AppStoreに申請
 - リリース後にAppStoreからアプリをダウンロードし、本番確認

本記事では、(3)と(4)については触れません。
(4)の本番確認は特に課金周りのチェックを行います。
というのも、iOSにおいて以下の2点がリリース前では確認することが出来ないためです。

 1. IAP(Production)での動作
 -  AskToBuyでの動作
   -  ちゃんと確認をすることは出来ません(途中までしか出来ない)。

サーバ側でレシート検証をおこなっていることからも、上記の確認をすることが重要です。


## (1)環境用意
iOSアプリ開発なので、利用するマシンはMacになります。
Jenkins用にしろ、iOSアプリ開発用にしろやる作業は基本的に一緒です。

### 環境構築
Ansibleを利用してセットアップしています。
詳細については本記事では書きませんが、すでに色々な記事があるので、そちらを参考にしてもらえれば良いかと思います。

#### Xcodeについて
iOSアプリ開発では、Xcodeは必需品になります。

しかし、Xcodeのダウンロードについては手動で行っており、そこが課題の1つとなっています。
また、Jenkinsマシンでは、複数のXcodeを利用するタイミングがあるため、AppStoreからのインストールではなくAppleDeveloperCenterから直接dmgファイルでダウンロードしインストールしています。

### iOS開発用の各種設定
定期的に以下の作業が発生しますが、スクリプトを用意しておりそれを実行するだけで良いようにしています。
こちらもAnsibleを利用して、出来るようにしています。

 - iOS開発用の証明書インストール
 - Apple Dev Centerへの登録
  - デバイスの追加
  - Provisioning Profileの更新
 - Provisioning Profileの設置

Jenkinsマシンの場合は、いちいちマシンにログインしスクリプトを実行するのは面倒であるため、Jenkinsのjobを用意して全Jenkinsマシンに対して実行できるようにしています。
上記のやり方については、以前書いた[記事](http://qiita.com/tarappo/items/ca5b828e6a0761cd23b6)が多少参考になります。


## (2)開発/検証
開発時における内容としてはおおまかに以下のようなことがあります。

   1. 実装(サーバ/アプリ)
   - サーバ側コードの単体テストの実行
   - サーバのデプロイ
   - アプリの自動テストの実行
   - アプリのビルド
   - マニュアルテスト

ただし、必ずしも全てをおこなうわけではありません。
開発(バグ修正含む)のパターンは以下のとおりで、それぞれおこなう内容は書いてあるとおりです。

 - 両方共に実装
    - (1)から(6)まで全て
 - サーバ側だけの実装
    - (1)(2)(3)(4)(6)
 - アプリ側だけの実装
    - (1)(4)(5)(6)

本記事では(2)(3)についての詳細は記述しませんが、(2)(3)についてもJenkinsを利用しています。


### (1)実装
サーバ側、アプリ側共にGitHubにPRを作り、特定のアクションをおこなうと特定のJenkinsのjobが実行されるようにしています。

#### Jenkinsのjob
以下の3種類があり、(2)に関してはあくまでもビルドが出来ることの確認用であり成果物であるipaファイルは保存しないようになっています。

 1. サーバ側ロジックの単体テスト
 - アプリのビルド
 - アプリの自動テスト(開発用環境で動作)


#### 実行タイミング
上記のjobの実行タイミングは以下の2種類になっています。

 1. 新しいコードがpushされた
 - コメントに「test this please/build this please」を書いた

レビューの関係から、PRは必ず出すようになっていますが、上記により「アプリが問題なくビルドされた」とか「テストが問題なく動いたと」いうことが分かってからレビューを行うことが出来るようになっています。

ただし、「アプリのビルド」や「アプリの自動テスト」の実行は時間がかかるためアプリ側では(2)のみでの実行にしています。

アプリ側においては、以下の様な状態にしています。
<img width="591" alt="スクリーンショット 2015-12-23 14.19.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/37642/77f6beac-9468-2eff-0315-8f56da103e0d.png">


#### 利用しているプラグイン
 - Github pull request builder plugin
   - https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin


### (4)アプリの自動テストの実行
利用しているテスティングフレームワークは以下の2種類です。

 - XCTest
 - XCUITest

アプリの自動テストでは結合テストを行っており、実際にサーバと通信しながらのテストをおこなっています。
課金のテストなどUI操作が絡む箇所についてはXCUITestを利用しています。
E2Eのテストとしては、上記の様にXCUITestを使って一部書いていますが、iOS9以降しか利用できないこともあり、今後はAppiumも使おうと思っています。


テストコードの作成方針としては、必要なテストは何かを考えてから、実装するようにし何でもかんでも実装するようにはしていません。
また、テストカバレッジの取得を定期的におこなって可視化するのは数字を追うだけになりがちなのでやっていません。
※これ以外にも自動テストの話は色々と書けることが多いので、今度別の記事で書こうかと思います。

#### Jenkinsのjob
「開発用」「検証用」「本番用」の3種類のjobがあります。

全てのjobに対して以下のパラメータを設定できるようになっています。

 - GitHubのbranch/tag
 - device
   - テストを実行する端末名
 - info.plist内
   - CFBundleIdentifier
      -「開発用」「検証用」「本番用」で値が異なるため。
   - CFBundleVersion
   - CFBundleShortVersionString
 - DEVELOPER_DIR
   - Xcodeのメジャーアップデートがある時に本パラメータを利用し、新旧バージョンでビルド出来るようにしています。


リリースしたバージョンのtagを指定することで、その時点でのテストが実行できるようにはなっています。
ただし、現時点では以下の問題があります。

 - リリースした時のバージョンとビルド環境が異なっていると正確なテストにはならない
   - その時に作成されたipaファイルを元にテストをするほうがベター
 - サポートするバージョン全ての自動テストを一気に動かすようにしていない
   - 理由は簡単で凄い時間がかかりすぎてしまうため。


deviceの値として、現時点ではJenkinsマシンに接続されたiOS端末の名前をビルドのパラメータ(選択)のデフォルト値として設定しています。
しかし、上記方法は端末が変わったり増えたりした時に更新が必要だったりとスマートではないためJenkinsマシンに接続されているiOS端末を取得しパラメーターで使えるようにしようと思っています。


#### 実行タイミング
理由は後述していますが、検証担当者がテストを実行するべき必要があるタイミングで実行しています。


#### テストの実行結果
JUnit形式で出力させ、「JUnitテスト結果の集計」を使っています。
JUnit形式で出力させる例としては以下の様な感じです。

```
xcodebuild test -workspace sample.xcworkspace -scheme sample -destination "name=${device}"  2>&1 | bundle exec ocunit2junit
```


### (5)アプリのビルド
アプリのビルドは、設定する項目が色々とあるため、専用のビルドスクリプトを用意しています。
このビルドスクリプトでは、info.plist内の値を変更したりipaファイルの生成までおこなっています。


#### Jenkinsのjobの種類
「開発用」「検証用」「本番用」「実際にリリースするアプリ用」と4種類のjobを用意しています。

上記のうち「実際にリリースするアプリ用」以外は以下のパラメータを設定できるようになっています。

 - GitHubのbranch
 - info.plist内
   - CFBundleIdentifier
      -「開発用」「検証用」「本番用」で値が異なるため。
   - CFBundleVersion
   - CFBundleShortVersionString
 - DEVELOPER_DIR
    -  Xcodeのメジャーアップデートがある時に本パラメータを利用し、新旧バージョンでビルド出来るようにしています。


また、「実際にリリースするアプリ用」をiTunes Connectにアップロードするのも、このjob内でおこなっていますが、説明は省きます。


#### 実行タイミング
理由は後述していますが、検証担当者がアプリを利用する必要があるタイミングで実行しています。

ただし、「実際にリリースするアプリ作成用」はリリース担当者がリリースOKと判断したタイミングで実行します。

#### 成果物の保存
jobの実行で作られたアプリ(ipaファイル)は「Copy Artifact Plugin」を使って保存するようにしています。

また、「実際にリリースするアプリ作成用」のjobが実行されるタイミングで、Jenkins APIを叩き「開発用」「検証用」「本番用」「実際にリリースするアプリ作成用」のjobの最新の成功したjob noに対して以下の操作をおこないます。

 - ビルドを保存する
 - 説明に「実際にリリースするアプリ作成用」のjob noのURLを記載
    - これは「開発用」「検証用」「本番用」のjobのみ


#### 利用しているプラグイン
 - Copy Artifact Plugin
    - https://wiki.jenkins-ci.org/display/JENKINS/Copy+Artifact+Plugin


### (6)マニュアルテスト
マニュアルテストはその名の通り、手動での確認です。
検証担当者は、「開発者」「テスター(的な人)」なのかは、サーバ側の環境により異なります。

自動テストで担保出来ない箇所については、マニュアルテストで担保します。
従って、マニュアルテストが必要ないと判断できる場合は実施しません。

### その他(補足)
#### Jenkinsのjobで共通して行っていること
(主に)共通しておこなっている事として、Slackへの通知をおこなっています。

利用しているのは以下の2種類のプラグインです。

 - Slack Plugin
   - wikiのページでは「Slack Plugin」とありますがJenkinsのプラグイン追加画面では「Slack Notification Plugin」となっています。
   - https://wiki.jenkins-ci.org/display/JENKINS/Slack+Plugin
 - Groovy Postbuild Plugin
   - https://wiki.jenkins-ci.org/display/JENKINS/Groovy+Postbuild+Plugin

「Groovy Postbuild Plugin」は、jobの実行者にmentionを飛ばす必要に利用しています。
例として、以下のようなことをやっています。

 - サーバの自動テスト実行
   - 失敗時に実行者に通知
 - アプリのビルド
   - 成功時に実行者に通知

#### ビルドパイプラインについて
Jenkinsのjobをそれぞれ用意しているので、ビルドパイプラインをすることは出来ます。

想定される流れとしては以下の様な形かと思います。

 1. サーバ側の単体テスト実行
 - 1が成功なら、サーバのデプロイ
 - 2が成功なら、アプリの自動テスト実行
 - 3が成功なら、アプリのビルド実行
 - 4が成功なら、マニュアルテスト
 - 5が無事に終われば次の環境へデプロイ

1度は上記の1〜4までをおこなうようなパイプラインを作ったものの以下の様な問題があったため、現時点ではおこなっていません。

 - サーバのデプロイ
   - 手作業でやる必要があるところもたまに発生し常に自動でデプロイできるとは限らない
 - 実機での自動テスト
   - 時間がかかる
      - この後にアプリのビルドもあるため、マニュアルテストが始まるまでに時間がかかる
   - UIが絡むテストの場合、タイミング次第で落ちることがある

そのため、現在はビルドパイプラインは設定しておらず、一部は手動で実行するようにしています。

今行っているのは「アプリのビルド」の後に「アプリの自動テスト」を動かすようにしています。
検証担当者が、アプリをインストールし実行している間に、アプリの自動テストが動いて問題があれば教えてくれるという形です。

##### 利用しているプラグイン
 - Parameterized Trigger Plugin
   - https://wiki.jenkins-ci.org/display/JENKINS/Parameterized+Trigger+Plugin

# 最後に
現時点では、上記のような流れでiOSアプリの開発を行っています。
フローの整備とJenkins(など)を用いた自動化により、開発や検証に集中できるようになってきています。


とはいえ、まだまだ課題はあります。
今後の課題としては、以下を取り組む予定です。

 - アプリの自動テストを実行する実機のバリエーションを増やす
    - 接続されている実機を選択してテストを実行できるJenkinsプラグインの開発
    - AWS Device Farmなどの利用
 - 各バージョンのipaファイルを用いてのアプリの自動テスト
 - アプリの自動テストの改良
    - やれることをもっと増やしていく
    - 高速化
 - ビルドパイプラインの改良