EthnaでUnit Test その1

EthnaでのUnit Testに関する情報があまり無い気がするので、
以前まで抱えていたEthnaで作っていたプロジェクトで培ったTDDのノウハウと気をつけたい事についてちょこっと書きたいと思います。
あ。環境はPHP4で、EthnaCVS版がベースでの話です。

設定の方法等は公式を参照してください。
http://ethna.jp/ethna-document-dev_guide-misc-unittest.html

SimpeTestに関してはWEB+DB PRESSの29号にわかりやすい特集が掲載されています。
http://www.gihyo.co.jp/magazines/wdpress/archive/Vol29

はじめに

Ethnaではテストツールとして、SimpleTestというものが使われています。
CakePHPSymfonyでも採用されているみたい。

僕自身、テスト駆動開発Ethnaを使って行うのが初めてだったので、
PhpUnitやPhpUnit2といったものは使ったことがありません。
なので他のテストツールと比較はできないです。ゴメンナサイ。

いきなり拡張

SimpleTestのassert類って、ちょっとイケてないよねーって感じることがあったので独自assertを作ったほうが便利。
まず、Ethna_UnitTestCaseを継承したAppid_UnitTestCaseを作りましょう。
後々、共通したプロパティやメソッドが欲しくなることが多いです。後々書きます。

アクション、ビュー以外のテストケース

Ethnaのデフォではadd-action-test,add-view-testといったコマンドしか用意されてませんが、
AppManagerやAppObject、Pluginなんかに対してもテストを書くことができます。
個人的にそういったロジックレベルでのテストケースのほうがよく書くし、大事かなぁとか思います。

そういったのを簡単に作れるEthnaコマンドがあれば*1便利だなぁ…。

テストケースの数が増えると大変なのでappの下にtestといったディレクトリを作成し、

みたいにAppid_UnitTestCaseを継承したテストケースを置いて、

Appid_UnitTestManager.php内にテストケースを読み込むように記述すればOKです。
先ほど作成したAppid_UnitTestCase.phpもここで読み込んじゃえばOKです。

include_once 'Appid_UnitTestCase.php';

class Appid_UnitTestManager extends Ethna_UnitTestManager
{
    var $testcase = array(
        'Hoge' => 'app/test/Hoge_TestCase.php',
    );
}

テストケースのクラスの命名規則は、キー名_TestCaseとなってるようです。

メソッド

SimpeTestのassertで引っかかった点として、

assertTrue()

Trueって名前だら

if ($hoge === true)

って比較をするのかと思ったら内部では最終的に、

if ((boolean)$hoge)

みたいな比較をしやがるので、オブジェクトをassertTrue()に突っ込んでも青になっちゃいます。

僕が設計するロジックって、成功時はtrue,駄目なときは
それに見合ったErrorやWarningをraiseすることが多いのですが、
以下のような時に困ります。

Hoge_Managerでfooというメソッドがあった場合に、

/**
 * 何か処理を行う
 * 
 * @access public
 * @param int $n 処理結果に関わる何らかの数字
 * @return mixed(TRUE: 処理成功: EthnaError: 処理失敗)
 */
function foo($n)
{
	return $n === 1 ? true : Ethna::raiseError('1以外は駄目だよ');
}

Hoge_TestCaseでfooメソッドに対する以下のようなテストケースがあった場合、両方とも青になってしまいます。

function test_foo()
{
    $m =& $this->backend->getManager('Hoge');
    $this->assertTrue($m->foo(1));	
    $this->assertTrue($m->foo(2));
}

同じようにassertFalseに空のarrayや0等を突っ込んでも青になります。
SimpleTestをガンガン使い出すと結構困ります。

独自assertの作成
$this->assertTrue($m->foo(1) === true);

とかすればまあ大丈夫なのですが、毎回毎回そんなのを書くのは面倒ですね。
なので独自のassertを作成しましょう。
SimpleTestの構造上、本格的にassertを追加するのは面倒くさそうだったので必要最低限ですむようにします。

ここで、さっき作ったAppid_TestCaseにメソッドを追加します。
例として、引数がEthnaErrorなのかどうかを見極めるメソッドを作ってみます。

Appid_UnitTestCaseに

    /**
     * 引数がEthna_Errorか検証する
     * 
     * @access public
     * @param mixed $object チェックする値      
     * @param string $message メッセージ.
     * @return boolean (TRUE: Ethna_Errorオブジェクト, FALSE: Ethna_Errorオブジェクトでない)
     */
    function assertEthnaError($object, $message = '%s')
    {
        $this->assertTrue(
            Ethna::isError($object) === true,
            sprintf($message, " assertion got Ethna_Error")
        );
    }
    
    /**
     * 引数がEthna_Errorではないか検証する
     * 
     * @access public 
     * @param mixed $object チェックする値
     * @param string $message メッセージ.
     * @return boolean (TRUE: Ethna_Errorオブジェクトでない, FALSE: Ethna_Errorオブジェクト)
     */
    function assertNotEthnaError($object, $message = '%s')
    {
    	$this->assertTrue(
            Ethna::isError($object) === false,
            sprintf($message, " assertion got not Ethna_Error")
        );
    }

というのを書けば、各テストケースで下記のように記述できます。

function test_foo()
{
    $m =& $this->backend->getManager('Hoge');
    $this->assertNotEthnaError($m->foo(1));	
    $this->assertEthnaError($m->foo(2));
}

SimpleTestは型比較系に弱い気がするので、
必要に応じてassert等をどんどん作っていけば便利になりますよ。


と、今回はここまでです。間違い等あればごめんなさい。

*1:近いうちBoBppさんがリリースしてくれるような気がします。(ムチャフ