この記事では以下のことについての手順を簡単に説明します。

  • iOSアプリのテストをTravis CIで動かす
  • Coverallsにコードカバレッジを渡す

Objective-C勉強会@東京 6月 でNSDateについて発表してきた | Web scratch で書いていたように、
NSDateについてのライブラリ azu/NSDate-Escort · GitHub を書いてて、このライブラリは、

Travis CIでテストを動かしてBuild Status
Coveralls でコードカバレッジCoverage Status をとっています。

NSDate-Escort を例にして設定を見ていきます。

iOSアプリのテストをTravis CIで動かす

CLIでテストを動かすには

xcodebuild を直接使ってテストを走らせる方法と

xctool を使ってテストを走らせる方法がよく取られてると思います。

NSDate-Escort では、xctoolの設定ファイルである.xctool-argsにテストを動かす設定を書いておき、
単純に xctool test を実行すればテストを走るようにしてあります。

また、この時にプロジェクトのschemeにxctool用のschemeを追加しておくといいです。

Screenshot 2013 06 12 23 16 38

xctoolは Find Implicit Dependencies をサポートしていないため、自動でPodsのlinkしてくれないので、
xctool用のschemeにはTest Bundleより前に 依存関係となるPodsを追加しておきましょう。

後は、忘れずにxctool用のschemeにはSharedにチェックを入れておきます。
Sharedにチェックを入れると NSDate-Escort.xcodeproj/xcshareddata/xcschemes/xctool.xcscheme という感じの場所にスキームが移動されるので、一般的なObjective-C.gitignoreを設定してるならGitで管理できる位置になると思います。

Schemeをちゃんと含めないとTravis CIからスキームがないといわれて xctool test でエラーになったりします。

Screenshot 2013 06 12 23 20 59

.travis.yml

Travis CIの設定は.travis.ymlファイルに書きますが、NSDate-Escortでは以下のような感じです。

language: objective-c
before_install:
  - sudo easy_install cpp-coveralls
  - brew update
  - brew uninstall xctool # xctool 0.1.4 broken
  - brew install https://raw.github.com/mxcl/homebrew/308395c2fc03399acbc24d226b8558f18e509e5b/Library/Formula/xctool.rb
  - gem update cocoapods
script:
  - xctool test GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES
after_success:
  - ./script/coveralls.sh -r ./ -e Pods -e Tests

xctool 0.1.4が壊れてる対処はそのうち要らないはず 又 gem update cocoapodsは別にいらなかったりするので最小限の場合だと以下で足りると思います。

language: objective-c
before_install:
  - sudo easy_install cpp-coveralls
script:
  - xctool test GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES
after_success:
  - ./script/coveralls.sh -r ./ -e Pods -e Tests

今のTravis CIは brew install xctool --HEAD されたものがデフォルトで入ってるようです。。(なんでHEAD…)

before_install: after_success: はCoveraills用なので、今は無視して

scriptで xctool test を実行していることがわかれば十分です。

script:
  - xctool test

これで、Travis CIでテストを動かす設定ができてるので後はTravis CIにログインしてGithubのレポジトリを選ぶだけです。

今回は Test Targetがひとつなので、”.xctool-args”に依存したものになっていますが、複数のTarget(iOS/Macとか)をやる場合はMakefileなどを書いて xctoolに引数を渡して実行されば問題ないと思います。

AFNetworking/Rakefile at master · AFNetworking/AFNetworking · GitHub ではRakefileを書いて、複数のTargetのテストを動かしています。

Coverallsにコードカバレッジを渡す

次に本題の Coveralls でコードカバレッジを取る方法。

若干語弊があって、コードカバレッジ自体はXcodeでビルドするときに、Build Settingの Generate Test Coverage FilesInstrument Program Flow をYESにしておくと、 gcno, gcda というファイルを吐き出してくれるので、このファイルをCoverallsに渡すことでCoverallsでコードカバレッジを閲覧できるようになります。
コマンドラインからこの設定を渡すには xctoo test GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES という感じで渡せば、プロジェクト側に設定しなくて有効に出来ます。

gcno, gcda から、 gcov コマンドで .gcov ファイルとして取得できるのでこれを使って、Coverallsにコードカバレッジの結果を送るようなスクリプトを作ります。

まとめると

xctool test(オプション付き) -> gcno, gcdaを生成 -> gcovコマンドで .gcov を生成

幸いにもgcovに対応したCoverallsのCLIであるcpp-coveralls があるので、これを利用します。(シェルスクリプトで無理やりやる方法もあるそうです PHP 拡張 (PECL) の開発で Coveralls を利用してみる|PHP|忍者ツールズ開発ブログ)

ここで先程の最小の.travis.ymlを振り返って見ると

language: objective-c
before_install:
  - sudo easy_install cpp-coveralls
script:
  - xctool test GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES
after_success:
  - ./script/coveralls.sh -r ./ -e Pods -e Tests

  1. before_install:cpp-coveralls をインストールしています。
  2. script: で コードカバレッジファイルを作る設定をつけてテストを走らせます
  3. after_success: で コードカバレッジファイル を coveralls に送るシェルスクリプトを呼び出しています
    (ここでの引数は、そのままcoveralls コマンドに渡しています)

次に、 coveralls.sh を見ていきます。

以下の事をしたかったのでシェルスクリプトじゃなくてBashスクリプトですが…

#!/bin/bash

trim() { trimmed=$1 trimmed=${trimmed%% } trimmed=${trimmed## }

<span class="nb">echo</span> <span class="nv">$trimmed</span>

}

# declare BUILT_PRODUCTS_DIR CURRENT_ARCH OBJECT_FILE_DIR_normal SRCROOT OBJROOT declare -r xctoolVars=$(xctool -showBuildSettings | egrep '(BUILT_PRODUCTS_DIR)|(CURRENT_ARCH)|(OBJECT_FILE_DIR_normal)|(SRCROOT)|(OBJROOT)' | egrep -v 'Pods') while read line; do declare key=$(echo "${line}" | cut -d "=" -f1) declare value=$(echo "${line}" | cut -d "=" -f2) printf -v "trim ${key}" "trim ${value}" # https://sites.google.com/a/tatsuo.jp/programming/Home/bash/hentai-bunpou-saisoku-masuta done < <( echo "${xctoolVars}" )

declare -r gcov_dir="${OBJECT_FILE_DIR_normal}/${CURRENT_ARCH}/"

## ======

generateGcov() { # doesn't set output dir to gcov... cd "${gcov_dir}" for file in ${gcov_dir}/*.gcda do gcov-4.2 "${file}" -o "${gcov_dir}" done cd - }

copyGcovToProjectDir() { cp -r "${gcov_dir}" gcov }

removeGcov(){ rm -r gcov }

main() {

# generate + copy generateGcov copyGcovToProjectDir # post coveralls ${@+"$@"} # clean up removeGcov
}

main ${@+"$@"}

おおまかにやっていることは

  1. コードカバレッジファイル(gcno, gcda) がある場所を見つける
  2. gcov-4.2 コマンドで .gcov ファイルを作成する
  3. プロジェクトファイルの所に、 .gcov ファイル をコピーする(cpp-coveralls の制約)
  4. coveralls コマンドで coveralls にコードカバレッジ情報を送信する

最初に 1. の コードカバレッジファイル(gcno, gcda) がある場所を見つけるために、
xctool -showBuildSettings を利用しています。(xcodebuildにも同じようなのがあります)

これは、プロジェクトのBuild SettingをCLIから取得できるので、出された情報を元にコードカバレッジファイルの場所を取り出します。

そのまま値としては持ってなくて、恐らく ${OBJECT_FILE_DIR_normal}/${CURRENT_ARCH}/ のディレクトリに吐き出されているので、
その辺を処理してるのが while read line; あたりでぐるぐるまわしながらとってきて $gcov_dir に入れています。(もっとスッキリ書きたい…)

gcovコマンドは gcovgcov-4.2 があると思いますが、gcov-4.2の方を使って、コードカバレッジファイルを全部 .gcovを作成しておきます。

そして、gcovファイルをコピーしてきて 4.coveralls コマンドを実行しています。

結果的には coveralls -r ./ -e Pods -e Tests という感じで実行されているはずです。
これはrootを./にして再帰的に、コードカバレッジの対象ファイルを見つけるという感じで、-eで指定するのは除外ディレクトリです。

-t で coveralls のトークンを指定すればローカルでも動かせます(もしくは.coveralls.ymlを置く)が、Travis CIとcoveralls連携をしておくとトークンの指定はしなくても、Travis CIで走らせれば自動的にcoverallsに送ってくれます。

後は、coverallsにログインして使いたいGithubのレポジトリをチェックしておけば、

Travis CI -> テスト成功 ->  coveralls.sh -> coverallsに送信

という事をやってくれて、以下のようにコードカバレッジをcoverallsで見ることができます。

おわり

これで、Travis CIでテストを走らせて、コードカバレッジをcoverallsで取る方法についての説明は終わりです。
シェルスクリプトとか結構雑なので、もっと良い書き方があればPull Request送りつけるとか記事を書いてくれるといいかと思います。

NSDate-Escort はコードカバレッジを100%にすることを目的に書き始めました。

おまけ

Travis CIやcoverallsのようにステータス画像を発行してくれるサービスが最近は多くなってます。

Build Status Coverage Status

そういうサービスについて最近まとめたスライド書いてたので一緒に置いておきます。

上記のスライドには入ってないですが、最近CocoaPodsのステータスバッジを取れるCocoapod Badgesというサービスもできてました。