署名なしでiOSアプリのビルド&単体テスト〜Pipelines編〜

## 「Azure Pipelines」とは？

Azure DevOps Servicesの一種です。
Microsoftが提供しているクラウドのCI/CDサービスです。

Azure DevOpsのアカウントを持っていれば使えます。

(以下「Pipelines」と呼ぶ)

## 環境

- Xcode：10.1 (10B61)
- Swift：4.2.1
- Carthage：0.31.2
- CocoaPods：1.6.0.beta.2

## Pipelinesでジョブの作成

Azure DevOpsにログインしてプロジェクトを作成します。
https://dev.azure.com/

プロジェクトの作成については割愛します。

__左のツリーで「Pipelines > Builds」を選択
「+ New」をクリック > 「New build pipeline」を選択__
<img width="587" alt="スクリーンショット_2019-01-12_12_35_33.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/1ac78c60-b081-aede-da86-583f1c1fee47.jpeg">

 __「Use the visual designer」を選択__
<img width="986" alt="スクリーンショット_2019-01-12_12_39_10.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/946a0d1c-b987-6fdc-ffb8-93ede019ecf4.jpeg">

PipelinesではジョブをYAMLファイルで作成することが推奨されていますが、GUIで作成することもできます。
YAMLファイルより直感的に作成できるため、今回はビジュアルデザイナーを使って作成します。

__ソース、プロジェクト、リポジトリ、デフォルトのブランチを選択
「Continue」をクリック__
<img width="918" alt="スクリーンショット_2019-01-12_12_45_41.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/60bd930a-61c8-a82c-69b6-272133454682.jpeg">

私はAzure DevOpsのReposでソース管理しているので、「Azure Repos Git」を選択します。
今回は単体テストを実行するのが目的のため、ブランチは「develop」を選択しています。

__「Xcode」を検索して「Apply」をクリック__
<img width="1014" alt="スクリーンショット_2019-01-12_12_57_15.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/18f555f4-979b-143f-a753-491fd44e4706.jpeg">

iOSアプリを対象とするのでXcodeのテンプレートを選択します。

ビジュアルデザイナーのメイン画面が表示されます。
<img width="1009" alt="スクリーンショット 2019-01-12 13.05.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/e959b7bb-10b5-3b2e-0a37-d9758e6628d2.png">

__情報量が多くて挫折しかけますが、諦めないでください__ 。
ひとつひとつ説明していきます。

## ジョブの詳細([Tasks]タブ)

ジョブは複数のタスクを組み合わせることで成り立っています。
[Tasks]タブでタスクの編集が行えます。

### Pipeline

ジョブの基本的な情報を入力する箇所です。

編集前はこのようになっています。
<img width="1009" alt="スクリーンショット_2019-01-12_13_05_54.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/29c29ee3-e0e1-f31f-a3e3-f636dcc63169.jpeg">

Nameはジョブの名前です。
ジョブの内容を一言で表せる名前が望ましいです。
今回は単体テストの実行なので「{プロジェクト名}-iOS-UnitTests」のような名前がよさそうです。
<img width="458" alt="スクリーンショット 2019-01-12 13.20.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/7a173222-7f51-5f47-f7bf-008353cd3b57.png">

Agent poolは実行環境です。
iOSアプリはmacOSでしかビルドできないため、「Hosted macOS」一択です。
<img width="461" alt="スクリーンショット 2019-01-12 13.21.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/c228fb0c-7b35-221f-b47c-d55e5a114fcd.png">

「Pool information」をクリックすると、選択した環境の情報を参照できます。
<img width="567" alt="スクリーンショット_2019-01-12_13_24_35.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/191a16ed-a665-880c-6b2e-3501af7e98ab.jpeg">

ローカルで使っているツールとバージョンが異なっていないか確認します。
CocoaPodsとCarthageのバージョンは特に重要です。
もし異なっている場合、ツールをインストールするタスクが追加で必要になることがあります。

Workspace or project pathはワークスペースまたはプロジェクトファイルのパスです。
[…]をクリックして選択します。
<img width="455" alt="スクリーンショット 2019-01-12 13.30.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/6521a277-a57a-ded8-3600-a3773d8f2cb2.png">

Schemeは対象のスキーマです。
App Centerと異なり選択でなく手動で入力するので、間違えないように気をつける必要があります。
<img width="452" alt="スクリーンショット 2019-01-12 13.32.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/f1d5db72-47ea-c8be-369e-e4ecb5179b04.png">

Xcode versionは文字通りXcodeのバージョンです。

2019/01/12現在、「Xcode 10」を選択すると「10.0」が使われます。
私はXcode 10.1を使いたいため、パスを直接指定します。
パスは以下に記載されています。
https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/macos/macos-Readme.md#xcode
<img width="460" alt="スクリーンショット_2019-01-12_13_41_50.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/b3d5d645-db02-6327-1b80-d73a0742f643.jpeg">

こちらに記載されていないバージョンは使えないので注意です。

全て入力すると以下のようになります。
<img width="467" alt="スクリーンショット 2019-01-12 13.43.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/834d089f-2610-6061-7698-3f9fcda94ef0.png">

### Get sources

ソースの所得先を選択する箇所です。
先ほど選択したので、基本的には変更不要です。
<img width="920" alt="スクリーンショット_2019-01-12_13_47_36.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/eb27d721-a703-d0be-30ab-ca49d77c29a1.jpeg">

ただ、先ほどは指定できなかった項目があるので、必要に応じて変更します。
<img width="332" alt="スクリーンショット 2019-01-12 13.58.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/754b0c2e-66ef-6a00-1b19-0003713d515f.png">

### Agent job

ジョブの本体です。
この下に並んでいるのがジョブ内のタスクです。
<img width="1011" alt="スクリーンショット_2019-01-12_14_03_19.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/719b06a9-4350-6dea-292f-cf322a57411c.jpeg">

ジョブ自体の設定は必要に応じて変更します。
私は特に変更しませんでした。

## タスクの詳細

ジョブ内のタスクを見ていきます。

### 不要なタスクの削除

まず、今回のジョブに不要なタスクを削除します。

__右クリック > Remove seleceted task(s) から以下のタスクを削除__

Install an Apple certificate
Install an Apple provisioning profile
∵今回は署名しないため

Copy Files to: …
Publish Artifact: drop
∵今回は何もダウンロードしないため

Test with Visual Studio App Center
∵今回はUIテストを実施しないため

Deploy **/*.ipa to Visual Studio App Center
∵今回はデプロイしないため

### タスクの入れ替え

ビルドに成功した場合のみ単体テストを実行したいため、以下のタスクをドラッグ&ドロップで入れ替えます。
Xcode build
Xcode test

ここまで行うとタスクは以下のようになります。
<img width="521" alt="スクリーンショット_2019-01-13_12_31_49.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/9820d794-fca0-c72e-e3c8-2d2d5e0fd500.jpeg">

### タスクの追加・編集

ここからはタスクを追加・編集していきます。

#### Set Xcode path

何も指定しない場合、Swiftはデフォルトのバージョンが使われます。
SwiftのバージョンはXcodeと紐付いているため、Xcodeのパスを指定することでSwiftのバージョンを決定します。
※Xcodeのパスを直接指定していない場合、このタスクは不要かもしれません。

__ジョブの[+]をクリック > 「Command Line」タスクを検索して「Add」をクリック__
<img width="1014" alt="スクリーンショット_2019-01-13_12_40_27.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/40e1b991-bb0a-3dc8-4c62-20cd40378f2b.jpeg">

ドラッグ&ドロップで一番上に持っていき、以下の項目を編集します。

- Display name(任意)  
Command Line Script→Set Xcode path
- Script

```bash
sudo xcode-select --switch /Applications/Xcode_10.1.app/Contents/Developer
```
先ほど指定したXcodeのバージョンと合わせる必要があります。

<img width="507" alt="スクリーンショット_2019-01-13_12_52_02.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/78f706c2-9bd2-faa3-7f08-7c12ed87df1d.jpeg">

#### Install CocoaPods

デフォルトでインストールされているCocoaPodsと異なるバージョンを使う場合のみ必要なタスクです。

Set Xcode pathと同様に「Command Line」タスクを追加し、以下の項目を編集します。

- Display name(任意)  
Command Line Script→Install CocoaPods
- Script

```bash
sudo gem install cocoapods --pre
```
私はβ版のCocoaPodsを使いたいため、 `--pre` を付けています。

#### Pod install

CocoaPodsで管理しているライブラリをインストールするタスクです。

__ジョブの[+]をクリック > 「CocoaPods」タスクを検索して「Add」をクリック__
<img width="477" alt="スクリーンショット_2019-01-13_14_05_08.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/c036139a-659f-8ecb-172c-f453b7dfee7b.jpeg">

以下の項目を編集します。

- Display name(任意)  
pod install→Pod install
- Working directory  
[…]をクリックし、 `Podfile` が格納されているフォルダを指定します。  
ルートフォルダに格納されている場合は省略できます。
- Force repo update  
OFF→ON  
ONにしないとビルドエラーになることがあります。

<img width="505" alt="スクリーンショット_2019-01-13_14_09_24.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/702bbd21-fdb8-866a-1dfb-34358f5429f6.jpeg">

#### Install Carthage frameworks

Carthageで管理しているライブラリをインストールするタスクです。
CocoaPodsのように専用のタスクは用意されていないため、「Command Line」タスクでインストールします。

以下の項目を編集します。

- Display name(任意)  
Command Line Script→Install Carthage frameworks
- Script

```bash
export GITHUB_ACCESS_TOKEN={GitHubのアクセストークン}
carthage bootstrap --platform iOS --cache-builds && echo '*** Resolved dependencies:' && cat 'Cartfile.resolved'
```
CarthageはGitHubへのアクセス数が多いため、アクセストークンを設定しないと呼び出し制限に引っかかってしまいます。
アクセストークンの取得方法は[こちらの記事](https://rcmdnk.com/blog/2013/12/05/mac-homebrew/)などを参考にしてください。

- Working directory  
[…]をクリックし、 `Cartfile.resolved` が格納されているフォルダを指定します。  
ルートフォルダに格納されている場合は省略できます。

<img width="537" alt="スクリーンショット_2019-02-27_16_55_21.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/fd53f48f-1f57-c997-e8c4-73b9bb2c12ce.jpeg">

#### Xcode build

Xcodeでビルドするタスクです。

以下の項目を編集します。

- Destination platform(任意)  
Default→iOS and watchOS
- Simulator(任意)  
iPhone 7→iPhone XR  
シミュレータの種類は以下に記載されています。  
https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/macos/macos-Readme.md#device-types
- Run this task  
Custom conditions→Only when all previous tasks have succeeded  
前のタスクが全て成功した場合のみビルドするようにします。

<img width="483" alt="スクリーンショット_2019-01-13_15_43_41.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/d7c984f5-02f4-346b-4348-c36cb2ad0b54.jpeg">

#### Xcode test

Xcodeでテストを実行するタスクです。

以下の項目を編集します。

- Signing style  
Project defaults→Do not code sign
- Simulator(任意)  
iPhone 7→iPhone XR  
OSも指定する場合は「iPhone XR,OS=12.1」
- Arguments(任意)  
-skip-testing:○○UITests  
UIテストを実行したくない場合はこちらのパラメータを追加します。
- Enabled  
OFF→ON  
- Run this task  
Custom conditions→Only when all previous tasks have succeeded

<img width="488" alt="スクリーンショット_2019-01-13_16_00_48.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/0a8199a6-6c58-6c70-4289-043875527624.jpeg">
<img width="487" alt="スクリーンショット_2019-01-13_16_01_05.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/c053de76-7fb2-04fe-2c34-2c1a4ec37b19.jpeg">

## ジョブの完成形

こちらがジョブの完成形です。
<img width="523" alt="スクリーンショット 2019-01-13 16.08.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/5a7e3976-1448-d05b-a302-6eea689f2f81.png">

## ジョブの保存・実行

ジョブを保存・実行するには「Save & queue」をクリックし、「Save & queue」を選択します。
保存のみ行う場合は「Save」を選択します。
<img width="669" alt="スクリーンショット_2019-01-13_16_16_54.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/c0d3b870-f176-d849-0cd8-4984c85fe561.jpeg">

Save commentにジョブの変更点などを記述し、「Save & queue」をクリックするとジョブが保存されて実行されます。
<img width="709" alt="スクリーンショット_2019-01-13_16_18_18.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/0f57a77a-facd-2561-2d07-0b2bb36e7272.jpeg">

## ジョブの実行結果

ジョブの実行が成功すると以下のように全てのタスクに緑色のチェックが付きます。
<img width="998" alt="スクリーンショット 2019-01-13 16.24.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/138245/5dfcf40d-1dbf-2a58-d651-eab038bd0041.png">

各タスクをクリックすることでログを確認できます。
<img width="1221" alt="スクリーンショット_2019-01-13_16_26_01.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/8ff24b5f-8dc2-5054-8ac4-9ab449f4de51.jpeg">

[Tests]タブで単体テストの実行結果を確認できます。
<img width="1021" alt="スクリーンショット_2019-01-13_16_28_10.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/7149ff8f-70f5-e21b-9593-28c21d090e6c.jpeg">

## ジョブの自動実行([Triggers]タブ)

[Triggers]タブで[Enable continuous integration]チェックをONにし、ブランチを指定することで、そのブランチのプッシュ時に自動でジョブを実行させることができます。
<img width="975" alt="スクリーンショット_2019-01-13_16_50_35.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/18a18126-4dbc-ce89-706e-f3128f926fe2.jpeg">

## おわりに

これで「ソースをプッシュ→ビルド→単体テスト」を自動化できました！

App Centerによるビルドより手間が掛かりますが、タスクを組み合わせることでより複雑なジョブを実行できます。

正直難しかったので、App Centerの自動ビルドのログを参考にタスクを組みました。
何か過不足しているタスクがありましたら教えていただけると嬉しいです。

## おまけ〜AppCenterとDevOpsの使い分け〜

両方使ってみて、以下のように使い分けるのがいいと感じました。

- Wiki、ソース管理、(複雑なCI/CD) → __Azure DevOps__
- (単純なCI/CD)、テストアプリの配布 → __App Center__

クラウドのCI/CDサービスは乱立気味で重複している機能もあるため、使い分けが難しいです。
他にオススメの使い分け方やサービスがありましたらコメント欄などに記載していただけると嬉しいです:relaxed:

## 参考リンク

- [Azure Pipelines | Microsoft Azure](https://azure.microsoft.com/ja-jp/services/devops/pipelines/)