読者です 読者をやめる 読者になる 読者になる

k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

GitHubに置いてるライブラリをCoverallsに対応した

PHP Test

自分のライブラリを Travis CI でのユニットテストに対応しましたが、このたびカバレッジレポートを公開できるサービス Coveralls にも対応したメモです。

恥ずかしながらこれまでカバレッジレポートの意義が理解できてなかったんですが、 php-coveralls を使えば簡単に Travis CI から Coveralls にレポートを転送して可視化してくれるようなので、試しにやってみました。

Coveralls への登録

CoverallsへのサインインはGitHubアカウントがあればOK。

入るとGitHubのリポジトリ一覧が表示されるので、Coverallsへのサービスフックを有効にしたいリポジトリのトグルスイッチを「ON」に設定します。

GitHubへのAPIトークンの設定なども、自動でやってくれます。

以下、 Volcanus_Routing の例

PHPUnit設定ファイルにロギングとカバレッジレポートの設定を追加する

PHPUnit設定ファイルにロギングとカバレッジレポートの対象/除外の設定を追加します。

phpunitコマンドのオプションでも可能ですが、なるべく多くの環境で設定を共有するために、設定ファイルにしてリポジトリに突っ込むのが通例のようです。

phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    backupGlobals="false"
    backupStaticAttributes="false"
    bootstrap="Tests/bootstrap.php"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    forceCoversAnnotation="false"
    processIsolation="false"
    stopOnError="false"
    stopOnFailure="false"
    stopOnIncomplete="false"
    stopOnSkipped="false"
    strict="true"
    verbose="true"
>
    <testsuites>
        <testsuite name="Volcanus_Routing">
            <directory suffix="Test.php">./Tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory>.</directory>
            <exclude>
                <directory>./Tests</directory>
                <directory>./vendor</directory>
            </exclude>
        </whitelist>
    </filter>
    <logging>
        <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
    </logging>
</phpunit>

上記では <filter> でカレントディレクトリ以下をカバレッジレポートの対象に、 Tests および vendor ディレクトリ以下を除外としています。

また <logging> で phpunitコマンド実行時のレポートを標準出力に出すようにしています。

Coverallsとの連携にはClover形式のXMLを出力する必要があるのですが、ローカルでの実行時には不要だし自動テストも遅くなるので、こちらには記述せず Travis CI で実行するphpunitコマンドに引数で指定しました。

詳しくは PHPUnitマニュアルの 第18章 ログ出力 および 付録C XML 設定ファイル を参照。

Composerでphp-coverallsを追加する

satooshi/php-coveralls を Composer でインストールしますが、このライブラリ(Volcanus_Routing) 自体の動作には必要ないものなので、composer.json には "require-dev" として設定します。

composer.json

{
    "name": "volcanus/routing",
    "homepage": "https://github.com/k-holy/Volcanus_Routing",
    "type": "library",
    "description": "request-URI routing for page-controller scripts.",
    "version": "0.3.3",
    "license": "MIT",
    "authors": [
        {
            "name": "k-holy",
            "email": "k.holy74@gmail.com"
        }
    ],
    "autoload": {
        "psr-0": { "Volcanus\\Routing": "." }
    },
    "target-dir": "Volcanus/Routing",
    "require": {
        "php": ">=5.3.3"
    },
    "require-dev": {
        "satooshi/php-coveralls": "dev-master"
    }
}

php-coveralls が何をやってくれるかというと、作者の方のブログ記事 PHPでもCoverallsできるよ! - satooshi@blog によると

php-coverallsは、これ自体でカバレッジを計測するわけではなくて、計測されたコードカバレッジAPIに渡している。

とのことで、Travis CIでPHPUnitを実行して出力したカバレッジレポートを、APIを利用してCoverallsに登録するための coveralls コマンドをインストールしてくれるようです。

なので流れとしては、Travis CI の実行環境で composer install --dev して php-coveralls をインストール → phpunit コマンドでレポートを出力 → coveralls コマンドで Coveralls に送信 となります。

Travis CI用の設定ファイルにphp-coverallsインストールとcoverallsコマンドを追加する

Travis CI用の設定ファイル .travis.yml に前述の流れを追加します。

.travis.yml

language: php

php:
  - 5.3.3
  - 5.3
  - 5.4
  - 5.5

before_script:
  - composer install --dev --no-interaction --prefer-source

script:
  - mkdir -p build/logs
  - phpunit -c phpunit.xml --coverage-clover build/logs/clover.xml

after_script:
  - php vendor/bin/coveralls -v -c .coveralls.yml

composerコマンドで --dev オプションを付けて php-coveralls をインストールして、ログファイル出力用のディレクトリを作成して、phpunitコマンドで --coverage-clover オプションを付けて Coveralls用のレポートを出力します。

phpunit.xmlリポジトリルートに置いてあるので、phpunitコマンドの -c オプションは必要ないはずですが、自分が忘れないために明示してます。

coveralls コマンドでは -c オプションで php-coveralls の設定ファイルを指定します。

-v オプションについては Wiki の Troubleshooting ・ satooshi/php-coveralls Wiki に「Failed to submit json_file!」とあったので指定してみました。

php-coveralls用の設定ファイルをリポジトリに追加する

.coveralls.yml

src_dir: .
coverage_clover: build/logs/clover.xml
json_path: build/logs/coveralls-upload.json
exclude_no_stmt: true

"src_dir" で計測対象となるソースファイルのルートディレクトリを指定、"coverage_clover" で Clover形式のXMLレポートファイルの場所を指定します。ここは前述の phpunitコマンドの --coverage-clover オプションでの出力先に合わせます。

"json_path" でCoveralls APIで登録するためのJSONファイルの出力先を指定します。内部動作を追ってはいないので分かりませんが、README の通りの内容で問題なく動作しました。

"exclude_no_stmt" は Wiki の Troubleshooting ・ satooshi/php-coveralls Wiki に「Want to ignore interface files from coveralls stats」とあったので指定してみました。

インタフェースやメソッドの実装が無い例外クラスなどを定義することがありますが、それらをレポート結果から除外してくれるオプションのようです。

詳しくは README satooshi/php-coveralls を参照。

Coverallsで結果を確認する

ここまで対象のリポジトリに必要な設定ファイルなどを追加した上で、Coveralls上でも連携が有効になっているか確認します。

REPOS または UPDATES から「ADD REPO」です。

OKならGitHubにpushすると、まずは Travis CI への連携でユニットテストが実行された後、順次 Coveralls にも更新内容が上がってきます。

こんな感じで、UPDATES にはどのリポジトリのどのブランチに誰がpushしたかと、その結果カバレッジがどう変化したかの概要が一覧で表示されてきます。

リポジトリの詳細に入ると、現在のカバレッジとともにビルド履歴が表示されます。

(なぜか TIME 列の値がおかしいですが、どうせコミッタは自分だけなので、とりあえずおいときます…。)

ビルドの詳細に入ると、FILES の欄にファイルの一覧が表示されます。 ALL で全て、CHANGES でこのビルドで変更されたファイルのみ表示されます。

(上記の画像は適切なフィルタ設定や .coveralls.yml で exclude_no_stmt を有効にする前のものなので、例外クラスのファイルが軒並み「0%」で表示されてたり、テスト用の中身の無いPHPファイルが表示されています…)

ちなみにこの一覧、JavaScriptで非同期に取得しているようでページャやソート機能も付いてますが、ブラウザの履歴には反映されないので、ちょっと使いづらいです。(Operaだけでしょうか?)

「MISSED」の多いファイルを開いて、コードの赤い部分に注目すると、こんな感じでテストケースで実行されていない箇所が把握できます。

手の抜きどころがもろバレです。これは恥ずかしい…!

Travis CIなどと同様に、READMEにバッジを表示するためのコードも取得できます。

リポジトリのREADMEにバッジを表示させてから「REFRESH」ボタンを押すと、この部分は表示されなくなります。

Coverallsの効果

Coveralls以前にカバレッジレポート自体これまで気にしてなかったのですが、テストケースがどの程度実際のコードの流れに反映されているかが分かるようになり、テストの書き忘れや自分が書くテストの癖に気付くことができました。

面白く感じたのは、あるクラスのリファクタリングを行ってクラスを分割したものの、新たに追加したクラスのユニットテストは面倒で書かなかったのですが、カバレッジは下がっていなかったことです。

要はリファクタリング前のテストケースの時点で今回別クラスに分離したロジックのテストも済んでいたため、テストケースを追加しなくても影響がなかったわけですが、これは安心感に繋がると思いました。

ただ、数値は高いに越したことはないのですが、100%を達成するためにコードの質を落としてしまうようでは本末顛倒です。

たとえば自分はメソッドの引数で連想配列を使ったよくある $options 形式の引数を扱う場合、無効なキーや想定外の値を検証して例外をスローするコードを仕込むことが多いです。

そういう連想配列の検証は廃止した方が動作は早くなるし、テストケースの網羅率も上げやすくなりますが、自分はよく配列のキーをtypoするので、これをやっておかないと安心して使えません。

しかし、そのテストを書くのは手間が惜しいということで、この場合については数値が下がっても構わないと割り切ることにしました。

ファイル単位でその結果を把握できていれば、時間ができた時に(これがなかなか難しいのですが…)そこを埋めることもできますし、レポートがいつでも確認できる状態にあることは大きな助けになるんじゃないでしょうか。