保存時にリアルタイムテスト実行でお手軽テスト

普段、Ethnaを使用して開発しているのだが、EthnaはWeb画面でテスト結果表示なので、どうしても一手間増えてしまう。
実際にテストケースを書いてブラウザで確認するのであれば、テストケースを書かずに、書いた実コードをブラウザで動作確認していた時と変わらず、Alt+Tab、Ctrl+R とかするのであれば、実コードを実行するいままでのやり方で済ませてしまい、なかなかテストケースを書くというやり方が浸透しない。
もしくは、最初はするけど、忙しくなるとしなくなるという状況でした。


テストケースを書くことで得られる安心感や、結果としてクオリティの担保になるというのは、理解してもらえるとは思うのだが、Alt+Tab、Ctrl+R という、今までと同じ手順ではない、テストを書く方が楽。という方法が確立できればいいのではないか。なんて考えていた。


つい先日、Ethnaの今後のロードマップを考える というか 話し合う飲み会があったのだが、そこでテストの話が少し出た。
id:sotarok から、ファイルの保存時に自動でテストを実行なんて話を聞いて、いつぞやのPHP勉強会で、kunitさんのTDDの話を思い出した。


ちょうど、今回途中参加した案件では、Ethnaではなく独自フレームワークっぽいもので作成だったので、TDDをすべく環境を整えてみた。

テストライブラリに何を使うか

ばっちりテストを書くというよりは、手軽に書きたいので、limeというテストライブラリを使おうとした。

limeでなるべく気軽にユニットテストを書く - id:anatooのブログ
[PHP] limeでTDDを体験する - DQNEO起業日記

このあたりが参考になるだろうか。
SimpleTestのようにクラスを作らずに手軽に出来るのが利点だ。


ところがどっこい、今回の案件は PHP4 でした。。。
limeは当然PHP5用なので、少し書き換えてPHp4用にしようかと思ったが、テストライブラリ自体が信用できない自分が書いたコードというのは意味がない。。。


他に使えそうなのが、PHPUnit、SimpeTest、phptなどなど。
phptは、ちょっと手間がかかるので無理かも。とういことで、Ethnaで使い慣れた SimpeTest を使うことにしました。

お手軽テスト

お手軽にテストするために、テスト用にわざわざ何かを実行するのは手間なので、実行ファイルの最後にテストを書くようにした。
limeでなるべく気軽にユニットテストを書く - id:anatooのブログ
このあたりを参考にしてみる。

<?php
define('BASE', realpath(dirname(__FILE__).'/../'));
class ActionForm1
{
    //ここに実際のコード
}

// test
if (debug_backtrace()) return;
include_once BASE.'/lib/test_autorun.php';
include_once FR_BASE.'/lib/test_autorun.php';

class Test_ActionForm1 extends UnitTestCase
{
    var $ac;
    function setUp()
    {
        $this->ac = new ActionForm1();
    }

    function testPrepare()
    {
        //ここにテストコード
        $this->assertEqual($this->ac->hoge, 'hoge');
    }

}


こんな感じだ。
すこし長くなるが、vimで実行すると、保存後に :!php % とするとテストが実行できる。

test_autorun.php

テストの実行ファイル

<?php
require_once dirname(__FILE__) . '/simpletest/unit_tester.php';
require_once dirname(__FILE__) . '/simpletest/mock_objects.php';
require_once dirname(__FILE__) . '/simpletest/collector.php';
require_once dirname(__FILE__) . '/simpletest/default_reporter.php';

register_shutdown_function('test_autorun');

/**
 *    Exit handler to run all recent test cases if no test has
 *    so far been run. Uses the DefaultReporter which can have
 *    it's output controlled with SimpleTest::prefer().
 */
function test_autorun() {
    $candidates = array();
    foreach (get_declared_classes() as $class) {
        if (substr(strtolower($class),0,5) == 'test_') {
            $candidates[] = $class;
        }
    }
    $loader = new SimpleFileLoader();
    $suite = $loader->createSuiteFromClasses(
            reset($candidates),
            $loader->selectRunnableTests($candidates));
    $result = $suite->run(new DefaultReporter());
    if (SimpleReporter::inCli()) {
        exit($result ? 0 : 1);
    }
}

実行結果は

:!php form1.php
test_actionform1
OK
Test cases run: 1/1, Passes: 0, Failures: 0, Exceptions: 0

続けるにはENTERを押すかコマンドを入力してください

さらに一歩進んで

これだと、保存後にコマンドを実行する必要がある。
もう一歩進んで、保存時に自動的にテストが実行されるようにしてみる。

kansit という、rubyのスクリプトがあることを id:sotarok から教えてもらったのだが、開発しているサーバには rubyが入っておらず、rubyの環境を整えるよりは、作った方が早いので以下のようなものを作ってみた。

<?php
$dirs = array();
foreach ($argv as $arg) {
    $dir = realpath($arg);
    if ($dir && is_dir($dir)) {
        $dirs[] = $dir;
    }
}

$files = array();

while (1) {
    usleep(500);
    $target = array();
    foreach ($dirs as $dir) {
        foreach (glob($dir.'/*.php') as $file) {
            if (isset($files[$file]) && $files[$file] != filemtime($file)) {
                $target[] = $file;
            }
            $files[$file] = filemtime($file);
        }
    }
    if (count($target)) {
        exec('reset');
        foreach($target as $file) {
            echo ">>run test {$file}\n";
            //passthru('php -l '.$file);
            passthru('php '.$file);
            echo "\n";
        }
    }
}

コマンドラインで実行し、引数に監視対象のディレクトリを指定するというスクリプトです。
無限ループで監視を続けるので、停止をするときには、Ctrl+C で止めてください。

開発する環境

screen を縦分割します。
上の画面を 10行ほどにして、そこで、上記の監視スクリプトを実行しておきます。

で、下の画面で普通にコードを書いて、保存をした瞬間に上の画面で結果が表示されます。

Erase is backspace.
>>run test /path/to/form1.php
test_actionform1
OK
Test cases run: 1/1, Passes: 0, Failures: 0, Exceptions: 0

こんな感じ。


保存時に毎回見るわけではなく、気にせずがつがつ書いていけばいい。
PHPの構文エラーもすぐに気づくし、通ると思った瞬間に確認すればいい感じです。

それに、画面上部は常に視界の片隅に入るので、結果がNGの場合に長い行が出るので、気にしていなくても目に入ってくる。



ちょっとしたことだが、コードを書く作業の妨げにならずに何度もテストが実行され、コードが正しく動くというのに自信が持てるので、作成中多少手戻りが発生しても、影響範囲とかを考えたり悩んだりしなくて済むのが楽だ。