PHPでTAP(Test Anything Protocol)を使う

TAP(Test Anything Protocol)とは、テストを簡潔に記述するための書き方(プロトコル)のことです。

プロトコルというと難しそうですが、実際はとても簡単。標準出力に、以下のような出力を行うプログラムを作成すればよいのです。

    1..4
    ok 1 - Input file opened
    not ok 2 - First line of the input valid
    ok 3 - Read the rest of the file
    not ok 4 - Summarized correctly # TODO Not written yet 

要点は
(1) 最初の行に、テストの件数を表示する
(2) 2行目以降に、テスト結果(ok/not ok)・テスト番号・説明を表示する
以上。

なお、2行目以降の必須項目はテスト結果(ok/not ok)だけです。
テスト番号は無くてもエラーにはならないものの、あったほうがよい(実行予定テストの総数と、実際に走ったテストの総数が異なっている時に警告が出る)。
説明は、後で読む人のためにも書いておいたほうがよいです。

それでは実際に書いてみます。


tap.php
<?php

/**
 * @param int $number_of_tests
 */
function test($number_of_tests = 0)
{
    if ($number_of_tests > 0) {
        echo '1..', $number_of_tests, PHP_EOL;
    }
}

/**
 * @param        $got
 * @param        $expected
 * @param int    $test_number
 * @param string $description テストの説明
 * @param string $directive   「TODO」又は「SKIP」+ TODO/SKIPである理由
 */
function is($got, $expected, $test_number, $description = '', $directive = '')
{
    if ($expected === $got) {
        echo 'ok ', $test_number;
    } else {
        echo 'not ok ', $test_number;
    }
    if ($description !== '') {
        echo ' - ', $description;
    }
    if ($directive !== '') {
        echo ' # ' . $directive;
    }
    echo PHP_EOL;
    if ($expected !== $got) {
        echo '# got:      ', $got, PHP_EOL;
        echo '# expected: ', $expected, PHP_EOL;
    }
}
fizzbuzz.php
<?php
function fizzbuzz($n) {
  return $n;
}
fizzbuzz.t
#!/usr/bin/env php
<?php

error_reporting(E_ALL);

require_once 'tap.php';
require_once 'fizzbuzz.php';

test(15);
is(fizzbuzz(1), 1, 1);
is(fizzbuzz(2), 2, 2);
is(fizzbuzz(3), 'fizz', 3, '三の倍数');
is(fizzbuzz(4), 4, 4);
is(fizzbuzz(5), 'buzz', 5, '五の倍数');
is(fizzbuzz(6), 'fizz', 6, '三の倍数');
is(fizzbuzz(7), 7, 7);
is(fizzbuzz(8), 8, 8);
is(fizzbuzz(9), 'fizz', 9, '三の倍数');
is(fizzbuzz(10), 'buzz', 10, '五の倍数');
is(fizzbuzz(11), 11, 11);
is(fizzbuzz(12), 'fizz', 12, '三の倍数');
is(fizzbuzz(13), 13, 13);
is(fizzbuzz(14), 14, 14);
is(fizzbuzz(15), 'fizzbuzz', 15, '三の倍数 かつ 五の倍数');

TAPに従って書かれたテストを実行するには、proveというコマンドを使います。Perlの実行環境があるなら、インストールされているはず(Mac OS Xには標準で入っていますし、大抵のLinix/Unixには入っているでしょう)。

「prove fizzbuzz.t」を実行すると、「fizzbuzz.t .. Failed 7/15 subtests」と表示されます。15回のテストのうち、7回失敗したということです。「php fizzbuzz.t」を実行してみると、以下のような表示がされます。

not ok 3 - 三の倍数
# got:      3
# expected: fizz

最初のfizzbuzz.phpでは、fizzbuzzのロジックを組み込んでいなかったので、テストにこけるのは当たり前。fizzbuzz.phpの中身を差し替えましょう。

fizzbuzz.php
<?php
function fizzbuzz($n) {
    if ($n % 15 === 0) {
        return 'fizzbuzz';
    }
    if ($n % 5 === 0) {
        return 'buzz';
    }
    if ($n % 3 === 0) {
        return 'fizz';
    }
    return $n;
}
今度は、fizzbuzzのロジックをちゃんと組んであるので、テストに通るはずです。再度「prove fizzbuzz.t」を実行すると…「fizzbuzz.t .. ok」と表示されました!

このように、TAPを利用したテストは、*Unit等を使用したテストに比べ、(1) テストが簡単に書ける (2) 導入が非常に簡単である という特徴があります。

本格的なプロジェクトであれば、PHPUnit等のテスティングフレームワークを導入したほうがいいと思いますが、小規模なスクリプトのテストならTAPで十分でしょう。

<参考>

「同じコード」の同じって何さ - TAPのススメ
:tap.phpは、こちらのtap.rbのロジックをPHPで書き直し + 一部改変したものです。
TAP Specification:TAPの仕様