JenkinsでiOSアプリ開発の細々した作業を自動化する(その1 〜 Git → GHUnit → ビルド)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

サーバーサイドやWeb画面の開発では使われていましたが、ネイティブアプリ開発でJenkinsの導入が無かったため、今更ながら設定してみました。自動化って楽しいです。iOSのネイティブアプリ開発でJenkinsでGitリポジトリから取得し、GHUnit、ビルド、TestFlightで配信、HipChatに通知する環境を構築します。
今回はGitからソースの取得 〜 GHUnitでのUnitTest 〜 アプリのビルドまでです。
ビルドにはXcodeのコマンドラインツールが動作する環境が必要なのでMacが必要です。

流れ

  1. Gitリポジトリからソースファイルを取得(Jenkins GIT client plugin, Jenkins GIT plugin)
  2. GHunitでUnitTest(Xcode integration)
  3. ビルド(Xcode integration)
  4. TestFlightで配布(Testflight Plugin)
  5. HipChatで結果を通知(Jenkins HipChat Plugin)

現在の環境

Mac
Mini Mid 2010
OS
10.8.3(Mountain Lion)
Xcode
4.6.2
Jenkins
1.517

Jenkins Plugins

Git Client Plugin
1.0.6
Jenkins GIT plugin
1.4.0
Jenkins HipChat Plugin
0.1.4
Xcode integration
1.3.3
Testflight Plugin
1.3.8

各プラグインは Jenkinsの管理 → プラグインの管理 からインストールします

Git周りの設定

ソースコード管理

Git リポジトリの場所と対象のブランチを設定します。 trigger

ビルド・トリガ

フックしてビルドしたいところですが、社外Gitリポジトリを参照しているため、1分間に1回のポーリングとします。 trigger

GHUnitの設定

コマンドラインからの実行

最終的にJnkinsからShellの実行をするので、まずはコマンドラインからGHUnitを実行できるように設定します。
GHUnit guide_command_line Documentを参考にRunTests.shRunIPhoneSecurityd.shをxcodeprojのある場所に置きMakefileを作成します。
ここはハマりハマり進んだのですが、そのままの状態だと

Warning: CFFIXED_USER_HOME is not set!  It should be set to the simulated home directory.

というWarningが出るので、RunTests.shの11〜16行目をコメントアウトします。
もう一つiphonesimulatorが6.0以上を使用されている場合に

Unknown Device Type. Using UIUserInterfaceIdiomPad based on screen size

というメッセージが出て動作しません。iphonesimulator5.1を指定している場合は動作しますが、デフォルトやiphonesimulator6.0以上を指定している場合はこのメッセージが出てテストが実行されない為、iphonesimulator6.0以上でテストを実行する場合は、以下のようにmain.mを編集することで動作します。
ソースはここから引用しています

// main.m
#import <UIKit/UIKit.h>
#import "GHUnitIOS/GHUnit.h"

@interface MyUIApp : UIApplication
@end

@implementation MyUIApp

- (id)init
{
    self = [super init];
    if (self && getenv("GHUNIT_CLI") && [[[UIDevice currentDevice] systemVersion] doubleValue] >= 6.0) {
        __block BOOL done = NO;
        NSOperationQueue * queue = [[ NSOperationQueue alloc ] init ];
        [queue addOperationWithBlock:^{
            int status = [GHTestRunner run];
            if (status != 0) {
                NSString *reason = [NSString stringWithFormat:@"failed to test %d", status];
                @throw [NSException exceptionWithName:@"TestFailure" reason:reason userInfo:nil];
            }
            done = YES;
        }];
        
        while( !done ) {
            [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 5] ];
        }
    }
    
    return self;
}

@end

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, @"MyUIApp", @"GHUnitIPhoneAppDelegate");
    }
}

make test

で成功すればコマンドラインからの実行はOKです。

Jenkinsの設定

ビルド - シェルの実行

make clean && WRITE_JUNIT_XML=YES JUNIT_XML_DIR=tmp/test-results make test

clean と build をしてテスト結果を tmp/test-results に吐き出します。

ビルド後の処理 - JUnit テスト結果の集計

テスト結果の場所を設定します。
これで、ビルド実行すれば動作するはずですが、

xcodebuild: error: The workspace 'workspace_name' does not contain a scheme named 'test_target_name'.

とメッセージを出して失敗する場合があります。その場合は以下のようにテストターゲットのSharedのチェックを入れてあげれば解決します。

Xcodeでのビルドの設定

iOSのビルドに関しては↓でまとまっているのでそちらを参照してください。
@IT JenkinsでCIすればiOSアプリのビルドは、もう面倒くさくない (1/2)
設定後のキャプチャだけ貼ります。

まとめ

なんとか環境が作れました。GHunitの導入が現状だと正直テストを最初に作るのも、設定するのも手作業の部分が多くて導入しずらい感じがしています。Jenkins向けの設定は1度作ってしまえばしばらくは使えそうですが、iOSやXcodeのUpdateの時に対応できるのかに不安を感じました。ただ、Git pushでテストしてビルドしてくれるって素敵ですね。
次回はTestFlightHipChatの設定をします。

参考

mtgto's diary - CocoaPodsでGHUnit + OCMockなiOS開発環境を構築する
guide_command_line-template.markdown - GitHub
Jenkins + GHUnit - launch_msg error - KISS