2004年11月18日

PHPUnitの使い方

PHPUnitは、オブジェクトのテストにだけ使えるのかと思っていましたが、単純な関数の単体テストでも有効なんですね。(ドキュメントのサンプルは、関数のテストが載ってますが)

とういわけで、PHPUnitの単純な使い方を紹介します。

PHPUnitの使い方を参考にしました。(というかそのまんまです。すみません。というか、ありがとうございます。)


■1. 何をテストするか?
スクリプトにアクセスしたユーザのログをファイルに記録する関数をテストすることにします。

bool loginLogWriter(string user)

userを引数にとり、ファイルに、user、アクセス日時を書き込みます。
user、アクセス日時は、1回のアクセスに付き、1行をファイルの最後にタブ区切りで追記します。
書き込みが正常に終了した場合に、trueを返します。失敗した場合は、falseを返します。


何をテストするかを考えていくときりがありませんが、
タブ区切り、ファイル書込ということで、次の項目をテストすることにします。


  1. 引数で渡されたuserが正しく記録されていること。

  2. userにタブが含まれていても正しく記録されること。

  3. 書き込むファイルが存在しない場合にfalseを返すこと。

■2. テストのスケルトンの作成

まず、始めに(テストする関数を作る前に)、スケルトンを作ります。

test.php

<?php
require_once 'PHPUnit/GUI/HTML.php';
// Test Suites
require_once 'test/loginLogWriterTest.php'; //←テストが書かれたスクリプト

// run Test Suites
$suite = new PHPUnit_TestSuite("loginLogWriterTest"); //←テストするクラス名(1)
$result = new PHPUnit_GUI_HTML($suite);
$result->show();
?>


test/loginLogWriterTest.php

<?php
require_once 'PHPUnit.php';
require 'loginLogWriter.php'; //←テストするスクリプトをインクルード

class loginLogWriterTest extends PHPUnit_TestCase { //←(1)のテストするクラス名
    function loginLogWriterTest($name) //←コンストラクタ
    {
        $this->PHPUnit_TestCase($name);
    }

    function setUp()
    {
    }

    function tearDown()
    {
    }

    function testSetup() //←testで始まるメソッドがテストとして実行されます。
    {
        $this->fail("no implementation"); //←assertのメソッドについては、PHPUnit_Assertを参照
    }
}
?>

loginLogWriter.php

<?php
?>

とりあえずファイルだけ作ります。

ここまでで、とりあえず、test.phpを実行してみます。
(un)check all にチェックをつけて、run testsをクリックします。

failメソッドで、強制的に失敗させていますので、失敗していれば(橙色になっていれば)成功です。


■3. テストの作成
関数を作成する前に、まず、テストを作成します。
「1. 引数で渡されたuserが正しく記録されていること。」から作ります。


test/loginLogWriterTest.php

<?php
require_once 'PHPUnit.php';
require 'loginLogWriter.php';

class loginLogWriterTest extends PHPUnit_TestCase {
    var $logFile;
    function loginLogWriterTest($name)
    {
        $this->PHPUnit_TestCase($name);
    }

    function setUp() //←テストの前に呼び出されます
    {
        $this->logFile = "/tmp/test_log.log";
        if ( !file_exists($this->logFile) ) {
            touch($this->logFile); //←ログを書き出すファイルを生成しています
        }
    }

    function tearDown() //←テストの終了後に呼び出されます
    {
        unlink($this->logFile); //←生成したファイルを削除しています
    }

    function testWriteLog() //←testで始まるメソッドがテストとなります
    {
        $user = "hoge";
        $regexp = "|^".date("Y/m/d")."\t".date("H:i:").".*".$user."\t\n$|";
        loginLogWriter($user);
        $out = file($this->logFile); //←ファイルの内容を配列に取り出します
        $this->assertRegExp($regexp, $out[count($out)-1]); //←正規表現を使って出力結果をチェックします
    }
}
?>

ここまでで、テストを実行します。

まだ、loginLogWriter()を作成していないので、エラーになります。


■4. 関数の作成

まず、関数の定義だけを記述します。

loginLogWriter.php

<?php
function loginLogWriter($user){
}
?>

テストを実行します。

まだ、処理を書いていないので、エラーになりました。
テストスクリプトのエラーも出ていますが、気にしないでおきましょう。

さて、いよいよ、実際の処理を作ります。

loginLogWriter.php

<?php
function loginLogWriter($user){
    $logFile = "/tmp/test_log.log";
    $logData = array(date("Y/m/d"), date("H:i:s"), $user, "\n");
    $logData = implode("\t", $logData); //←区切りはタブとします
    $fp = fopen($logFile,"a");
    flock($fp,2);
    fwrite($fp, $logData);
    fclose($fp);
}
?>

テストを実行します。


■5. 次のテストです。
「2. userにタブが含まれていても正しく記録されること。」

同様に、テストを作ります。

test/loginLogWriterTest.php

<?php
require_once 'PHPUnit.php';
require 'loginLogWriter.php';

class loginLogWriterTest extends PHPUnit_TestCase {
    var $logFile;
    function loginLogWriterTest($name)
    {
        $this->PHPUnit_TestCase($name);
    }

    function setUp()
    {
        $this->logFile = "/tmp/test_log.log";
        if ( !file_exists($this->logFile) ) {
            touch($this->logFile);
        }
    }

    function tearDown()
    {
        unlink($this->logFile);
    }

    function testWriteLog()
    {
        $user = "hoge";
        $regexp = "|^".date("Y/m/d")."\t".date("H:i:").".*".$user."\t\n$|";
        loginLogWriter($user);
        $out = file($this->logFile);
        $this->assertRegExp($regexp, $out[count($out)-1]);
    }

    function testReplaceTab() //←追加したテストです
    {
        $user = "hoge\thoge";
        $repUser = str_replace("\t", "  ", $user); //←タブを空白2個に置換します
        $regexp = "|^".date("Y/m/d")."\t".date("H:i:").".*".$repUser."\t\n$|";
        loginLogWriter($user);
        $out = file($this->logFile);
        $this->assertRegExp($regexp, $out[count($out)-1]);
    }
}
?>

テストを実行します。

エラーになります。


次に、関数の処理を修正します。

loginLogWriter.php

<?php
function loginLogWriter($user){
    $logFile = "/tmp/test_log.log";
    $user = str_replace("\t", "  ", $user); //←追加しました
    $logData = array(date("Y/m/d"), date("H:i:s"), $user, "\n");
    $logData = implode("\t", $logData);
    $fp = fopen($logFile,"a");
    flock($fp,2);
    fwrite($fp, $logData);
    fclose($fp);
}
?>

テストします。

緑色のバーになれば、テスト完了です。

■6. 最後のテストです
「3. 書き込むファイルが存在しない場合にfalseを返すこと。」

同じ手順です。
テストを作ります。

test/loginLogWriterTest.php

<?php
require_once 'PHPUnit.php';
require 'loginLogWriter.php';

class loginLogWriterTest extends PHPUnit_TestCase {
    var $logFile;
    function loginLogWriterTest($name)
    {
        $this->PHPUnit_TestCase($name);
    }

    function setUp()
    {
        $this->logFile = "/tmp/test_log.log";
        if ( !file_exists($this->logFile) ) {
            touch($this->logFile);
        }
    }

    function tearDown()
    {
        unlink($this->logFile);
    }

    function testWriteLog()
    {
        $user = "hoge";
        $regexp = "|^".date("Y/m/d")."\t".date("H:i:").".*".$user."\t\n$|";
        loginLogWriter($user);
        $out = file($this->logFile);
        $this->assertRegExp($regexp, $out[count($out)-1]);
    }

    function testReplaceTab()
    {
        $user = "hoge\thoge";
        $repUser = str_replace("\t", " ", $user);
        $regexp = "|^".date("Y/m/d")."\t".date("H:i:").".*".$repUser."\t\n$|";
        loginLogWriter($user);
        $out = file($this->logFile);
        $this->assertRegExp($regexp, $out[count($out)-1]);
    }

    function testFileExists() //←追加しました
    {
        unlink($this->logFile);
        $user = "hoge";
        $this->assertFalse(loginLogWriter($user));
    }
}
?>

テストを実行します。

テストが失敗することを期待したのですが、緑色のバーになり成功しました。
確認のために、関数に戻り値trueを入れてみます。

loginLogWriter.php

<?php
function loginLogWriter($user){
    $logFile = "/tmp/test_log.log";
    $user = str_replace("\t", "  ", $user);
    $logData = array(date("Y/m/d"), date("H:i:s"), $user, "\n");
    $logData = implode("\t", $logData);
    $fp = fopen($logFile,"a");
    flock($fp,2);
    fwrite($fp, $logData);
    fclose($fp);
    return true; //←追加しました
}

テストします。

橙色になり、失敗しました。関数は、正常に実行されているようです。
ログを書き込むファイルを削除したはずなのに、と考えていると思い出しました。
fopenは、オープンするファイルがない場合には、ファイルを作成します。

テストが失敗の状態ですので、関数の処理を修正して、テストが成功することを確認します。

loginLogWriter.php

<?php
function loginLogWriter($user){
    $logFile = "/tmp/test_log.log";
    if ( file_exists($logFile) ) {
        $user = str_replace("\t", "  ", $user);
        $logData = array(date("Y/m/d"), date("H:i:s"), $user, "\n");
        $logData = implode("\t", $logData);
        $fp = fopen($logFile,"a");
        flock($fp,2);
        fwrite($fp, $logData);
        fclose($fp);
        return true;
    }
    else {
        return false;
    }
?>

テストを実行します。今度は成功しました。


このように、テストの作成→テストの失敗の確認→処理の作成→テストの成功の確認 を何度も繰り返して、プログラムを作成していけばいいのではないでしょうか。

Posted by hosco at 22:44

2004年10月14日

WindowsへのPHPのインストール

開発用に持ち歩いているノートに、PHPをインストールした。
正確には、1年前にインストールしていた、4.3.3を4.3.9にアップデートした。

1年前は、問題なくインストールできたはずなのに、うまくいかない。

結局、php4ts.dllをC:\Windows\System32にコピーして解決。

環境は、WindowsXP Professional、Apache 1.3.28

一応、インストールの手順を書いておきます。

php.netから、windows binaryをダウンロード。

phpは、C:\Program Filesの直下に置くことにする。

install.txtに従って、
Apacheのhttpd.confに、
LoadModule php4_module "C:\Program Files\php\sapi\php4apache.dll"
を追加。(1年前に設定していたみたいだ)

AddType application/x-httpd-php .php

も設定されている。

phpディレクトリ直下のphp.ini-recommendedを、C:\Windows直下にコピーして、php.iniにリネーム。
細かい設定は、後日することにしてとりあえずは、デフォルトのまま。

WindowsのPATHを設定。
「マイコンピータ」の「プロパティ」-「詳細設定」タブ−「環境変数」の「システム環境変数」のPathに追記するか、あるいは、コマンドプロンプトで、

c:\>Path=%Path%;c:\Program Files\php
とする。


ここで、Apacheをstartしてみる。

Cannot load c:/program files/php/sapi/php4apache.dll into server: (126) 指定されたモジュールが見つかりません。

怒られてしまう。なぜだ。

Pathをよく確認すると、なぜか、C:\Windows\Program Files\php になっている。
Program Filesは、Cドライブの直下のはず。再度設定を確認すると、環境変数の設定で、
%SystemRoot%\Program Files\php
としてしまっていた。

な〜んだ。

設定を正しくして、再度Apacheを起動。
今度は、

Failed to start the Apache service.
とでた。

だめらしい。

しょうがないので、C:\Windows\System32 に、php直下のphp4ts.dllをコピーしてみる。

再度、Apache起動。
やっと、起動しました。

Pathの設定をすれば、php4ts.dllをコピーしなくてもいいって、install.txtに書いてあるような気がするんだけど。
よくわからんです。

今日、やりたかったことは、pearのPHPUnitをインストールすることだったので、コマンドプロンプトから、。

C:\>cd C:\Program Files\php
C:\Program Files\php>go-pear

いろいろでてくる質問に適当に答えるだけで、pearコマンドのインストール完了。

Posted by hosco at 01:21