GitLabのCIでXCTestを自動的に行う

GitLabにはCI(Continuous Integration: 継続的インテグレーション) 機能があります。GitLabのCIを使えば、CI専用の別ソリューションを導入せずに、継続的インテグレーションを行えます。

そこで、GitLabにコードをプッシュしたときに、XCTestを動かして、macOSアプリやiOSアプリのユニットテストを実行することにしました。

この記事では、そのときに行った設定方法を紹介します。

スポンサーリンク

Runnerの登録

GitLabのCI/CDはRunnerが動いて処理を実行します。Runnerにはプロジェクト間で共有する「Shared Runner」と、特定のプロジェクト専用の「Specific Runner」があります。

今回は複数のプロジェクト(リポジトリ)から使用したいので、「Shared Runner」を登録しました。

Runnerのインストール

XCTestを実行するために使用するRunnerなので、XCTestを実行するMacにインストールします。ここでは、Mac Miniを使いました。

macOSへのGitLab Runnerのインストール方法は公式サイトに書かれています。

Documentation for GitLab Community Edition, GitLab Enterpris…

(1) ダウンロードします。ダウンロードするバイナリは、Intel Mac版とApple Silicon版とで異なります。ターミナルからcurlでダウンロードします。

  • Intel Mac版
sudo curl --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64"
  • Apple Silicon版
sudo curl --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64"

ダウンロードされたRunnerは/usr/local/bin/gitlab-runnerに保存されます。

(2) パーミッションを設定し、実行権限を追加します。

sudo chmod +x /usr/local/bin/gitlab-runner

(3) ホームディレクトリでGitLab Runnerをサービスとしてインストールします。

cd ~
gitlab-runner install

(4) gitlab-runnerのロケールを設定します。~/Library/LaunchAgents/gitlab-runner.plistファイルを開きます。このファイルはgitlab-runner installによって作成されます。

(5) 次のようにEnvironmentVariablesを追加します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

<!-- 省略 -->

	<key>EnvironmentVariables</key>
	<dict>
		<key>LC_ALL</key>
		<string>en_US.UTF-8</string>
	</dict>
</dict>

(6) サービスを開始します。

gitlab-runner start

Runnerの登録

MacにインストールしたRunnerをGitLabに登録します。

(1) Admin AreaRunnersを開きます。

(2) Register an instance runnerをクリックし、Show runner installation and registration instructionsをクリックします。

(3) 詳細手順が表示されるので、EnvironmentからmacOSを選択し、Command to register runnerという欄の横のコピーボタンをクリックして、コマンドをコピーし、Closeをクリックして閉じる。

(4) RunnerをインストールしたMac上のターミナルで、(3)でコピーしたコマンドをsudoなしで実行する。Mac上でXCTestを実行するときは、ユーザーモードで動かす必要があるので、sudoなしで実行する必要があります。

実行すると、設定を対話型で聞かれるので、順番に入力していきます。

(5) GitLabのURLを入力します。

Runtime platform                                    arch=amd64 os=darwin pid=10297 revision=76984217 version=15.1.0
Running in system-mode.                            
                                                   
Enter the GitLab instance URL (for example, https://gitlab.com/):
[http://code.rkdev.corp/]: 

(6) 登録トークンを確認されます。コマンド実行時に既にトークンを指定しているので、Enterキーを押すだけでOKです。

Enter the registration token:
[-ABCDEFG4567890YZABC]: 

(7) Runnerの説明を指定します。とりあえず、マシン名を入力しました。

Enter a description for the runner:
[MacMini2020.local]: MacMini2020

(8) Runnerに指定するタグを入力します。私の場合は、

  • Macである
  • Intel Macである
  • OSは常に最新を使う予定である

これらを踏まえて、次の3つのタグを設定しました。

  • mac
  • mac-intel
  • mac-intel-latest
Enter tags for the runner (comma-separated):
mac,mac-intel,mac-intel-latest

(9) メンテナンス用のメモを指定します。特にないので空欄のままにしました。

Enter optional maintenance note for the runner:

(10) Executerを設定します。公式サイトにmacOSアプリやiOSアプリの場合はshellを使うように書かれているので、shellを指定します。

Registering runner... succeeded                     runner=-ABCDEFG
Enter an executor: custom, parallels, shell, ssh, kubernetes, docker, docker-ssh, virtualbox, docker+machine, docker-ssh+machine:
shell

(11) 登録が成功すると次のように出力されます。

Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

(12) Admin AreaRunnersページを再読込すると、登録したRunnerが表示されます。

スポンサーリンク

CI/CDの設定

テスト用のリポジトリとコードを用意してCI/CDの設定を行います。

テストコード

ここでは、GitLabのCIによって、XCTestが動くことを確認したいだけなので、次のような必ずエラーになるテストコードと必ず成功するテストコードを入れたユニットテストにしました。何かをテストしているわけではなく、XCTestを動かすだけなので、テストコードと呼ぶのも憚れますが。。。

import XCTest
@testable import CITest

class CITestTests: XCTestCase {

    override func setUpWithError() throws {
    }

    override func tearDownWithError() throws {
    }

    func testExample() throws {
        XCTFail("TEST FAILURE")
    }
    
    func testSuccess() throws {
        XCTAssertTrue(true);
    }
}

Shared Runnerを有効化する

リポジトリ単位で、Shared Runnerを有効化する必要があります。

(1) プロジェクトのSettingsCI/CDを開く。

(2) RunnersExpandを開く。

(3) Enable shared runners for this projectをオンにする。

CI/CDの設定ファイルを追加する

次のように操作して、CI/CDの設定ファイルを追加します。

(1) Webブラウザでリポジトリのページを開き、Set up CI/CDリンクを開く。

(2) Create new CI/CD pipelineをクリックする。

(3) Browse templatesをクリックし、Swift.gitlab-ci.ymlを開き、内容をコピーする。

(4) 自分のリポジトリのPipeline Editorに戻り、コピーした設定を貼り付ける。

(5) Xcodeのプロジェクトファイル名、スキーム名などを変更する。例えば、次の設定ファイルは以下の値を変更しています。

設定
-project CITest.xcodeproj
-scheme CITest
-destination iOS Simulator,name=iPhone 13,OS=15.5
tags mac-intel-latest
-archivePath build/CITest
-archivePath build/CITest.xcarchive
-exportPath build/CITest.ipa
-exportProvisioningProfile “”
paths: build/CITest.ipa
stages:
  - build
  - test
  - archive
  - deploy

build_project:
  stage: build
  script:
    - xcodebuild clean -project CITest.xcodeproj -scheme CITest | xcpretty
    - xcodebuild test -project CITest.xcodeproj -scheme CITest -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.5' | xcpretty -s
  tags:
    - mac-intel-latest

archive_project:
  stage: archive
  script:
    - xcodebuild clean archive -archivePath build/CITest -scheme CITest
    - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/CITest.xcarchive" -exportPath "build/CITest.ipa" -exportProvisioningProfile ""
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  artifacts:
    paths:
      - build/CITest.ipa
  tags:
    - mac-intel-latest

CIによるテストのみが必要で、アーカイブは不要な場合には、archive_projectジョブを削除して、次のような設定ファイルにします。私の場合はこちらが必要でした。

stages:
  - build
  - test
  - archive
  - deploy

build_project:
  stage: build
  script:
    - xcodebuild clean -project CITest.xcodeproj -scheme CITest | xcpretty
    - xcodebuild test -project CITest.xcodeproj -scheme CITest -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.5' | xcpretty -s
  tags:
    - mac-intel-latest

-destinationオプションの指定については次の記事を参照してください。

(6) Commit changesをクリックする。

テスト

作成したテストコードをリポジトリにプッシュします。必ず失敗するテストなので、しばらくしてジョブが失敗し、エラー通知のメールが来ます。

プロジェクトのCI/CDPipelinesを開きます。失敗したジョブが表示されるので、failedをクリックします。

Failed Jobsタブを開きます。予定通りテスト失敗でエラーになっている場合は、次のように失敗したテストのテストケース(メソッド)名が出力されます。

2022-06-27 18:59:30.903 xcodebuild[18902:109049] [MT] IDETestOperationsObserverDebug: 0.000 sec, +0.000 sec -- start
2022-06-27 18:59:30.903 xcodebuild[18902:109049] [MT] IDETestOperationsObserverDebug: 104.154 sec, +104.154 sec -- end
Failing tests:
	CITestTests:
		CITestTests.testExample()

** TEST FAILED **

ERROR: Job failed: exit status 1

それ以外のエラーが出ている場合は、ログの内容を見て、設定などを見直してください。

UIテスティングについて

ここではXCTestを使ったユニットテストしか行っていませんが、UIテスティングについても変わりません。

XcodeのプロジェクトにUIテスティングのテストパッケージが入っていて、スキームのテストプランに登録されていれば、Xcodeから手動でテストするときと同じように、UIテストも自動的に実行されます。

トラブルシューティング

私が設定したときに遭遇したエラーと対応方法を掲載します。

ユーザーがログインしているときしか動かない

XCTestはログイン中のカレントユーザーが動かす必要があります。そのため、Runnerを動かすMacはログイン中になっている必要があります。

マシンを再起動したとき

公式の手順に従ってインストールすると、サービスとして登録されるので、マシンを再起動したときも、ログインさえすれば、Runnerが起動します。

状態がpendingのまま

状態がpendingのままで、パイプラインのbuild_projectを見ると、タグに対応したRunnerが存在しないというエラーが出ている。

このケースの場合は、次のような原因が考えられます。

  1. tagが間違っている。
  2. Shared Runnerを使おうとしているが、プロジェクトでShared Runnerが有効になっていない。
  3. Mac上のRunnerがユーザーモードで動作していない。

2はこのページの「Shared Runnerを有効化する」を参照してください。

3は私がはまりました。Runnerを登録するときに、GitLabが出力したコマンドラインはsudoが付いていて、そのまま実行すると、ユーザーモードでRunnerが動かないので、XCTestも動作しません。

シミュレータ名やOSバージョンが間違っている

この場合、build_projectのログの中に次のようなエラーが出力されていて、使用可能なシミュレータとOSの組み合わせが列挙されています。

xcodebuild: error: Unable to find a destination matching the provided destination specifier:

.gitlab-ci.ymlファイル内で指定する値を適切な値に修正しましょう。

プロジェクトのCI/CDEditorを開くと、.gitlab-ci.ymlファイルをWebブラウザ内で編集してコミットできます。

シミュレータ名やOSバージョンの指定方法については、次の記事を参照してください。

スポンサーリンク
最新情報をチェックしよう!