XcodeのコードカバレッジをHTMLなどで出力する方法

## はじめに

「Slather」というツールを使い、XcodeプロジェクトのコードカバレッジをHTMLなどで出力する方法を紹介します。

## 「Slather」とは？

Ruby製のXcodeプロジェクト用コードカバレッジレポート生成ツールです。
Xcodeのテストで出力したコードカバレッジを、XMLやHTML形式に変換できます。
Fastlane、Circle CIやTravis CIなどのCIサービスと連携できるようにもなっており、簡単に導入できるのが魅力です。

## 環境

- RubyGems：2.7.1
- Bundler：2.0.1
- Slather：2.4.6

## Slatherのセットアップ

### Slatherのインストール

Gemfileに以下を追加し、Bundlerからインストールします。

```diff:Gemfile
+ gem 'slather'
```

```shell-session
$ bundle install
```

### 設定ファイルの編集

プロジェクトのルートフォルダに `.slather.yml` というファイルを作成し、Xcodeプロジェクトの情報や出力パスなどを記述します。
設定ファイルを作成せず、必要な情報をコマンドラインの引数に渡して実行することもできます。

設定ファイルの書き方は公式をご参照ください。
https://github.com/SlatherOrg/slather

例：
CoberturaのXML形式でコードカバレッジを出力

```yaml:.slather.yml
coverage_service: cobertura_xml
xcodeproj: {プロジェクト名}.xcodeproj
workspace: {ワークスペース名}.xcworkspace
scheme: {スキーマ名}
source_directory: {プロジェクトのルートフォルダ}
output_directory: xml_report
ignore:
  - Pods/*
  - Carthage/*
  - "**/AppDelegate.swift"
  - "**/SceneDelegate.swift"
  - "**/Generated/*"
  - "**/UIParts/*"
  - "**/Views/*"
  - "**/Routers/*"
```

ignoreで指定するパスの先頭にワイルドカード( `*` )を使う場合、ダブルクォーテーション( `"` )で括らないと以下のエラーが発生します。

```shell-session
bundle exec slather coverage --html --output-directory html_report
bundler: failed to load command: slather (/Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/bin/slather)
Psych::SyntaxError: (.slather.yml): did not find expected alphabetic or numeric character while scanning an alias at line 10 column 5
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:456:in `parse'
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:456:in `parse_stream'
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:390:in `parse'
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:277:in `load'
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:578:in `block in load_file'
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:577:in `open'
  /usr/local/Cellar/ruby@2.6/2.6.5/lib/ruby/2.6.0/psych.rb:577:in `load_file'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/slather-2.4.7/lib/slather/project.rb:298:in `yml'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/slather-2.4.7/lib/slather/command/coverage_command.rb:102:in `project'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/slather-2.4.7/lib/slather/command/coverage_command.rb:73:in `setup_output_directory'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/slather-2.4.7/lib/slather/command/coverage_command.rb:44:in `execute'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/clamp-1.3.1/lib/clamp/command.rb:66:in `run'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/clamp-1.3.1/lib/clamp/subcommand/execution.rb:18:in `execute'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/clamp-1.3.1/lib/clamp/command.rb:66:in `run'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/clamp-1.3.1/lib/clamp/command.rb:140:in `run'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/gems/slather-2.4.7/bin/slather:17:in `<top (required)>'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/bin/slather:23:in `load'
  /Users/runner/runners/2.165.2/work/1/s/vendor/bundle/ruby/2.6.0/bin/slather:23:in `<top (required)>'
Slathering...
```

ignoreでコードカバレッジの計算に含めないファイルを指定できるのがいいです。
Xcodeによるコードカバレッジの算出では除外するファイルを(自分の知る限り)指定できません。

## スキーマの編集

コードカバレッジを出力するようにスキーマを編集します。
公式の通りに設定します。
https://github.com/SlatherOrg/slather#usage

私は「all targets」でなく、「some targets」にしてテストターゲットを除いて指定しています。

<img width="532" alt="スクリーンショット_2019-04-09_23_03_55.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/51ccdf1e-eed8-2826-3e0f-e5acb47d2649.jpeg">

## コードカバレッジレポートの生成・閲覧

### Xcodeによるテスト実行

Xcodeでテストを実行し、コードカバレッジを算出します。
Slatherではこのコードカバレッジを元にレポートを生成します。

### コードカバレッジレポートの生成

プロジェクトのルートフォルダで以下のコマンドを実行します。

```shell-session
$ bundle exec slather
```

HTML形式で出力するには `--html` オプションを付けます。

```shell-session
$ bundle exec slather coverage --html \
  --output-directory html_report
```

設定ファイルを作成していない場合、以下のように引数を追加して実行します。

```shell-session
$ bundle exec slather coverage -s \
  --scheme {スキーマ名} \
  --workspace {.xcworkspaceのパス} \
  {.xcodeprojのパス}
```

### コードカバレッジレポートの閲覧

例としてHTML形式のレポートを閲覧します。
「…/html_report/index.html」をWebブラウザで開くとドキュメントを閲覧できます。

<img width="1128" alt="スクリーンショット_2019-04-09_23_55_39.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/791110a3-c68c-3dd6-cf0b-2488b67b0648.jpeg">

ファイル名をクリックすると、どの処理が実行されているのか確認できます。

<img width="1241" alt="スクリーンショット_2019-04-09_23_56_25.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/40ef1464-d694-f053-b510-cc474b17565e.jpeg">
<img width="1220" alt="スクリーンショット_2019-04-09_23_56_37.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/138245/8d9c9207-51a0-5f49-a744-d956752eb99f.jpeg">

## 注意

Xcodeでテストを実行せずにコードカバレッジが生成されていないと、以下のエラーが発生します。
エラーから原因が特定できず、少し苦戦しました。
https://github.com/SlatherOrg/slather/issues/341

```
$ bundle exec slather coverage -s --scheme {スキーマ名} --workspace {ワークスペース名}.xcworkspace {プロジェクト名}.xcodeproj
Slathering...
no implicit conversion of nil into String

Are you sure your project is generating coverage? Make sure you enable code coverage in the Test section of your Xcode scheme.
Did you specify your Xcode scheme? (--scheme or 'scheme' in .slather.yml)
If you're using a workspace, did you specify it? (--workspace or 'workspace' in .slather.yml)
If you use a different Xcode configuration, did you specify it? (--configuration or 'configuration' in .slather.yml)

bundler: failed to load command: slather (/usr/local/bin/slather)
TypeError: no implicit conversion of nil into String
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/project.rb:184:in `initialize'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/project.rb:184:in `new'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/project.rb:184:in `profdata_coverage_dir'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/project.rb:460:in `find_binary_files'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/project.rb:399:in `configure_binary_file'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/project.rb:281:in `configure'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/lib/slather/command/coverage_command.rb:57:in `execute'
/Library/Ruby/Gems/2.3.0/gems/clamp-0.6.5/lib/clamp/command.rb:67:in `run'
/Library/Ruby/Gems/2.3.0/gems/clamp-0.6.5/lib/clamp/subcommand/execution.rb:11:in `execute'
/Library/Ruby/Gems/2.3.0/gems/clamp-0.6.5/lib/clamp/command.rb:67:in `run'
/Library/Ruby/Gems/2.3.0/gems/clamp-0.6.5/lib/clamp/command.rb:132:in `run'
/Library/Ruby/Gems/2.3.0/gems/slather-2.4.6/bin/slather:17:in `<top (required)>'
/usr/local/bin/slather:22:in `load'
/usr/local/bin/slather:22:in `<top (required)>'
```

## おまけ: SwiftPMで管理しているライブラリのソースを無視する

デフォルトでは、SwiftPMで管理しているライブラリのソースが対象になってしまいます。
無視するには `xcodebuild` コマンドの `-clonedSourcePackagesDirPath` オプションで `./SourcePackages` を指定し、設定ファイルに以下を追加します。

```diff_yaml:.slather.yml
ignore:
+   - SourcePackages/*
```

参考：https://github.com/uhooi/UhooiPicBook/commit/a14031e09331b65f703a59a56fffb45191bd29e6

## おわりに

Xcodeプロジェクトのコードカバレッジを、HTMLなどでグラフィカルに出力することができました！
これでiOSアプリ開発でコードカバレッジの提出を求められても安心です:relaxed:

## 参考リンク

- [SlatherOrg/slather: Generate test coverage reports for Xcode projects & hook it into CI.](https://github.com/SlatherOrg/slather)