iOSアプリをGitLab.com+ローカルGitLab Runnerでテストする

GitLab.com のプライベートレポジトリでソース管理している iOS アプリのテストを、個人所有の mac に入れた GitLab Runner と連動させ、コミットのたびにテストが走る環境を作ってみた。CI、継続インテグレーションというやつだね。テスト網羅率 (coverage) の採取も、少し苦労したけれど何とか実現。今日は、その方法を書き残しておく。

なお、GitLab CE (無償版オンプレGitLab)の機能範囲しか使っていないので、GitLab CE でも同様にセットアップできると思う。

背景

拙作の iOS アプリ DoF Table のソースコードを GitLab.com のプライベートレポジトリに置くことにした。この手のサービスは複数人での共有こそ真価を発揮すると思っており、個人所有の mac 一台を使って自分一人で開発している現状、メリットは無いだろうと思い込んでいたのだけれど、Objective-C から Swift に書き直す作業を進めている中、「自動テストを走らせていれば気づけたバグ」を何度か作ってしまった。そんなわけで、やっぱり CI 環境が欲しいなと思って環境構築に踏み切った次第。

なお GitLab を選んだのは、(1) 無料のプライベートレポジトリが使えること、(2) GitLab CE を仕事で構築・運用しているので慣れていること、(3) GitLab Runner を mac に配置できると知っていたこと、(4) GitLab.com に「何かあった」としても個人サーバーに GitLab CE を立てれば継続できる、といった理由による。

動作確認環境

  • macOS 10.15.4 (Catalina)
  • Xcode 11.4
  • GitLab.com (GitLab EE 12.10)
  • GitLab Runner 12.9.0

GitLab は進化が早いので、メニュー項目の名前などは日々変わっていく。以下では、あくまで本日2020年4月12日での表記例ということで。

GitLab にアプリのソースを登録する

まずは iOS アプリのソース一式を GitLab に登録する。そもそも登録済みならそれで良し、未登録なら同名のプロジェクトを GitLab で作ってから git push --force https://gitlab.com/ユーザー名/プロジェクト名.git を実行して丸ごと上書きする等、何らかの方法で GitLab で管理された状態にする。

テストを走らせる mac に GitLab Runner をセットアップする

続いて、自動テストを走らせたい mac に GitLab Runner をオフィシャルの手順でインストール・設定する。

  1. Runner のインストール)まず、以下コマンドを実行する:

    sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
    sudo chmod +x /usr/local/bin/gitlab-runner
    
  2. Runer の登録)続いて、その Runner で当該プロジェクト用のジョブが実行されるように Runner と GitLab プロジェクトの両方を設定する。たとえば:

    $ gitlab-runner register
    Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/)
    https://gitlab.com
    Please enter the gitlab-ci token for this runner
    ********    # 後述
    Please enter the gitlab-ci description for this runner
    macmini.local
    Please enter the gitlab-ci tags for this runner (comma separated):
    ios_12.4, ios_13.4, mac_10.15
    Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
    shell
    
    • "gitlab-ci coordinator URL" には、使っている GitLab の URL を指定する
    • "gitlab-ci token" に指定する値について。GitLab のプロジェクトの設定画面のうち「CI / CD」のページを開いて「Runners」のセクションを展開すると「Set up a specific Runner manually」という項目がある。ここに指定すべきトークン文字列が書かれているので、それをコピー&ペーストする
    • "gitlab-ci description" は、GitLab 上でこのRunner (≒mac) を表示するときに使われる名前。何でも良い
    • "gitlab-ci tags" には、その mac にインストールされた iOS シミュレーターで使える iOS バージョンを ios_12.4 などといった形でタグとして登録しておくと良い。詳細は後述する
    • "executor" には、shell を指定する。他の executor では恐らく不可だと思う
  3. (Runner のサービス化)最後に、mac へのログインと同時に Runner が起動するよう設定する:

    cd ~
    gitlab-runner install
    gitlab-runner start
    

いくつか細かい点を補足しておく。

  • 「サービス化」というのに「ログインと同時」とはどういうことか?と思われるかもしれないけれど、iOS アプリのテストではシミュレーターを起動する必要があるためユーザーログイン状態でないとうまくいかないらしい。なので gitlab-runner installsudo しない
    • ユーザーモードで iOS シミュレーターを使ったテストが実行されるので、ログイン中のデスクトップ画面で iOS シミュレーターが起動・表示されてテストが走る。しかし基本的にバックグラウンドで勝手に起動して勝手に終了してくれるので、「作業中にキーボードフォーカスが奪われる」といったことも起こらず開発作業の妨げにはならない(今のところ)
  • ちなみに GitLab はジョブを実行する際、そのジョブを実行できる Runner を探して選択する。そして、GitLab.com には無料で使える Runner がいくつか最初から提供されている。そのため、iOS アプリのテストには無料枠の Runner ではなく、今回登録した mac が必ず選択されるよう工夫する必要がある。このマッチングはタグを使うとうまくいく。具体例を挙げれば、Runner にタグ ios_12.4 を指定し、かつCI ジョブの方でも「ios_12.4 のタグが付いた Runner でのみ実行可能」と指定すれば良い。(GitLab 標準提供の Runner に偶然 ios_12.4 というタグが付いていたら困るんじゃあないかって?そんな名前のタグが付いているなら、たぶんその Runner は iOS 12.4 のシミュレーターがインストールされた Runner なのだろうから、まあ大丈夫でしょう)

CI の定義ファイルを登録する

GitLab Runner がインストールされ、プロジェクトと関連づけられたら後は CI の設定をする。DoF Table では以下のような内容で /.gitlab-ci.yml を作成した(ちなみに DofCalc は DoF Table の開発コードネーム)。

stages:
  - build

build_project:
  stage: build
  script:
    - DERIVED_DATA_PATH=$(mktemp -d) && echo $DERIVED_DATA_PATH
    - xcodebuild
      -quiet
      -project DofCalc.xcodeproj
      -scheme DofCalc
      -enableCodeCoverage YES
      -showBuildTimingSummary
      -derivedDataPath "$DERIVED_DATA_PATH"
      -destination 'platform=iOS Simulator,name=iPhone 8,OS=12.4'
      clean test
    - XCRESULT_FILE=$(find "$DERIVED_DATA_PATH" -name '*.xcresult') && echo $XCRESULT_FILE
    - xcrun xccov view
      --report
      $XCRESULT_FILE
  tags:
    - ios_12.4

この設定は、ソースを GitLab に push するごとにテストが走るミニマルな設定例となる。.gitlab-ci.yml についての詳細は、オフィシャルの解説を参照されたい。

上記設定におけるポイントを、いくつか。

  • ビルド・テストには xcodebuild コマンドを使用
  • テスト網羅率 (coverage) は -enableCodeCoverage オプションに YES を指定すると計測してくれる
    • しかしテスト網羅率はコンソールに表示されないので GitLab で検出できない
    • そこで、レポートを解釈して網羅率を表示するコマンドをテスト後に実行する
      • xcrun xccov view --report が、それ
      • ただ、xcodebuild を普通に実行するとレポートファイルはランダム生成されたディレクトリ下に出力されてしまい、xcrun に渡せない
        • 前もって mktemp で一次フォルダを作り、そこにレポートを出力するよう xcodebuild-derivedDataPath オプションを指定し、出力されたレポートのパスを確実に引き当てられるように工夫してある
  • tags には、Runner の方に登録した iso_12.4 を指定している。前述の通り、これによって今回登録した Runner が使われる

テスト網羅率の数値を検出するための正規表現を登録

最後に、GitLab のプロジェクト設定にて "Test coverage parsing" を設定しておく。これは「このパターンに合致するログが CI 実行時に検出されたら網羅率として認識する」というもので、これで GitLab に認識させると Merge Request の画面や CI/CD ジョブの一覧画面および詳細画面に網羅率が表示されるようになる。xcrun xccov view --report で生成したテスト網羅率レポート出力の場合、以下の正規表現を登録しておくと認識できるはず:

\w+\.app\s+(\d+\.\d+\%)

後書き

以上のセットアップは結構大変だったけれど、稼働し始めるとテスト頻度が上がり、満足感は高い。iOS アプリの CI に興味のある方の参考になれば幸い。