Do You PHP?    
Search Engine Optimization  php5 powerd  Valid XHTML 1.0!  Valid CSS!  このサイトのはてなブックマーク数 



last updated
2005/08/26

counter hits
since 1999/11/06


PHPUnit2 - 最強のユニットテスト自動化ツール for PHP5

cautionPHPUnit2 ver.2.3.0 β1からPHP5.1.0β1以降が必要になりますので要注意。

PHP5が正式リリースされてしばらく経ち、実際の開発で使われているという話もぼちぼち聞くようになりましたが、「開発」とくれば「テスト」は避けされませんね。最近の流行(?)としてはxUnitシリーズを利用して単体テストを行う場面が多いと思いますが、PHP5用としてはPHPUnit2がPEARに登録されています。

PHPUnit2は機能的にJUnit3.8.1とJUnitour1.2を組み合わせたモノになっていて、基本的にCLI版となっています。また、PHP4向けのPHPUnitにはない以下の機能があります。

  • テストコードの雛形の自動生成
  • TestDoxの生成
  • コードカバレッジ(網羅率)のレポート生成

今回はこの3つに絞ってまとめてみました。使用するサンプルスクリプトは、PHPUnitの時に使ったサンプルをPHP5に焼き直したものです(手抜きだ。。。(^-^;)。

●カートクラス - Cart.php
  
<?php
/**
 * ありがちなカートクラス
 */
class Cart
{
    private $items_;
    /**
     * コンストラクタ
     */
    public function Cart()
    {
        $this->items_ = array();
    }
    /**
     * 商品の追加
     */
    public function add($item_cd, $amount)
    {
        $this->_check($item_cd);
        if ($amount > 0) {
            $this->items_[$item_cd] += $amount;
        }
    }
    /**
     * 特定商品の削除
     */
    public function remove($item_cd, $amount)
    {
        $this->_check($item_cd);
        if ($amount > 0) {
            $this->items_[$item_cd] -= $amount;
            if ($this->items_[$item_cd] < 0) {
                $this->items_[$item_cd] = 0;
            }
        }
    }
    /**
     * 全商品のクリア
     */
    public function clear()
    {
        unset($this->items_);
    }
    /**
     * 特定商品の個数を返す
     */
    public function getAmount($item_cd)
    {
        $this->_check($item_cd);
        return $this->items_[$item_cd];
    }
    /**
     * 商品個数のチェック&初期化
     */
    public function _check($item_cd)
    {
        if (!isset($this->items_[$item_cd])) {
            $this->items_[$item_cd] = 0;
        }
    }
}
?>

●テストクラス - CartTest.php
  
<?php
require_once 'PHPUnit2/Framework/TestCase.php';
require_once "Cart.php";
?>
<?php
/**
 * Cartクラスのテストケース
 * PHPUnit2_Framework_TestCaseクラスを継承して作成する
 */
class CartTest extends PHPUnit2_Framework_TestCase
{
    private $cart_;
    /**
     * コンストラクタ
     */
    public function CartTest($name)
    {
        // 必ず指定するおまじない
        parent::__construct($name);
    }
    /**
     * テストの初期化(DB接続などの前処理が必要であれば)
     * テストメソッド毎にsetUp、tearDownが実行される
     */
    public function setUp()
    {
        $this->cart_ = new Cart();
    }
    /**
     * テストの終了処理(DB切断などの後処理が必要であれば)
     * テストメソッド毎にsetUp、tearDownが実行される。
     * 今回は使用しない
     */
    public function tearDown() {}
    /**
     * 以下実際のテスト
     * 「test」で始まるpublic function名がテスト対象となる
     */
    public function testInit()
    {
        // assertEqualsメソッドで2つの引数が等価かどうかを調べる。
        // 引数は「期待される値」「実際の値」の順。
        // ここでは、Cartに入っていない商品の個数が0であることを確認。
        $this->assertEquals(0, $this->cart_->getAmount("aaa"));
    }
    public function testAddPositive()
    {
        // Cartに商品を追加し、個数が正しいことを確認。
        $this->cart_->add("aaa", 1);
        $this->cart_->add("aaa", 2);
        $this->assertEquals(3, $this->cart_->getAmount("aaa"));
        // 別の商品を追加し、それぞれ個数が正しいことを確認。
        $this->cart_->add("bbb", 10);
        $this->cart_->add("bbb", 20);
        $this->assertEquals(3, $this->cart_->getAmount("aaa"));
        $this->assertEquals(30, $this->cart_->getAmount("bbb"));
    }
    public function testAddNegative()
    {
        // 個数に0を指定されたときは、個数に変化がないことを確認。
        $this->cart_->add("aaa", 1);
        $this->cart_->add("aaa", 0);
        $this->cart_->add("bbb", 0);
        $this->cart_->add("bbb", 10);
        $this->assertEquals(1, $this->cart_->getAmount("aaa"));
        $this->assertEquals(10, $this->cart_->getAmount("bbb"));
        // 個数に負数を指定されたときは、何もしない(個数に変化なし)
        // ことを確認。
        $this->cart_->add("aaa", -2);
        $this->cart_->add("aaa", -20);
        $this->assertEquals(1, $this->cart_->getAmount("aaa"));
        $this->assertEquals(10, $this->cart_->getAmount("bbb"));
    }
    public function testRemovePositive()
    {
        // 指定された個数分だけ減っていることを複数の商品で確認
        $this->cart_->add("aaa", 10);
        $this->cart_->remove("aaa", 3);
        $this->cart_->add("bbb", 10);
        $this->cart_->remove("bbb", 7);
        $this->assertEquals(7, $this->cart_->getAmount("aaa"));
        $this->assertEquals(3, $this->cart_->getAmount("bbb"));
    }
    public function testRemoveNegative()
    {
        // 個数が負数になる場合は、何もしない(個数に変化なし)
        // ことを確認。
        $this->cart_->add("aaa", 10);
        $this->cart_->add("aaa", -20);
        $this->cart_->add("bbb", 1);
        $this->cart_->add("bbb", -2);
        $this->assertEquals(10, $this->cart_->getAmount("aaa"));
        $this->assertEquals(1, $this->cart_->getAmount("bbb"));
    }
    public function testClear()
    {
        // 全ての商品の個数が0になることを確認
        $this->cart_->add("aaa", 10);
        $this->cart_->add("bbb", 5);
        $this->cart_->clear();
        $this->assertEquals(0, $this->cart_->getAmount("aaa"));
        $this->assertEquals(0, $this->cart_->getAmount("bbb"));
    }
}
?>

インストール手順

インストール手順は通常のPEARパッケージのインストールと同じく、以下のような感じです。完了後、PHPのインストールPREFIX(指定しなかった場合は/usr/local)/binに「phpunit」というコマンドが追加されていることを確認しましょう。今回は、PHPインストール時に「--prefix=/usr/local/lib/php5」を付けた環境で行いました。

●PHPUnit2のインストール
# /usr/local/lib/php5/bin/pear install --alldeps phpunit2
downloading PHPUnit2-2.2.0.tgz ...
Starting to download PHPUnit2-2.2.0.tgz (38,446 bytes)
..........done: 38,446 bytes
downloading Log-1.8.7.tgz ...
Starting to download Log-1.8.7.tgz (32,693 bytes)
...done: 32,693 bytes
downloading DB-1.7.6.tgz ...
Starting to download DB-1.7.6.tgz (124,807 bytes)
...done: 124,807 bytes
install ok: DB 1.7.6
install ok: Log 1.8.7
Optional dependencies:
'xdebug' PHP extension is recommended to utilize some features
install ok: PHPUnit2 2.2.0
#
# ls /usr/local/lib/php5/bin
pear*  php*  php-config*  php5*  phpextdist*  phpize*  phpunit*
# 

テストコードの雛形の自動生成

まず、テストコードの雛形の自動生成についてです。開発がある程度の規模になってくるとテストコードを書くだけでも大変ですが、テスト対象のクラスを元にテストコードの雛形が作成されるだけでもかなり楽になると思います。

手順としては、テスト対象のクラスと同じディレクトリで以下のコマンドを実行します。この時、テスト対象のクラスのファイル名は、クラス名と合わせておく必要があります。今回の場合だとクラス名が「Cart」なので、ファイル名は「Cart.php」としておく必要があります。

●雛形の作成
$ /usr/local/lib/php5/bin/phpunit --skeleton Cart
PHPUnit 2.2.0 by Sebastian Bergmann.
Wrote test class skeleton for Cart to CartTest.php.
$ 

生成されたテストクラスは以下の通りです。この後、各メソッドにテスト内容を記述することになります。

●自動生成されたCartTest.php
  
<?php
if (!defined("PHPUnit2_MAIN_METHOD")) {
    define("PHPUnit2_MAIN_METHOD", "CartTest::main");
}
require_once "PHPUnit2/Framework/IncompleteTestError.php";
require_once "PHPUnit2/Framework/TestCase.php";
require_once "PHPUnit2/Framework/TestSuite.php";
require_once "PHPUnit2/TextUI/TestRunner.php";
require_once "Cart.php";
/**
 * Test class for Cart.
 * Generated by PHPUnit2_Util_Skeleton on 2005-04-23 at 23:15:11.
 */
class CartTest extends PHPUnit2_Framework_TestCase {
    public static function main() {
        $suite  = new PHPUnit2_Framework_TestSuite("CartTest");
        $result = PHPUnit2_TextUI_TestRunner::run($suite);
    }
    /**
    * @todo Implement testAdd().
    */
    public function testAdd() {
        throw new PHPUnit2_Framework_IncompleteTestError;
    }
    /**
    * @todo Implement testRemove().
    */
    public function testRemove() {
        throw new PHPUnit2_Framework_IncompleteTestError;
    }
    /**
    * @todo Implement testClear().
    */
    public function testClear() {
        throw new PHPUnit2_Framework_IncompleteTestError;
    }
    /**
    * @todo Implement testGetAmount().
    */
    public function testGetAmount() {
        throw new PHPUnit2_Framework_IncompleteTestError;
    }
    /**
    * @todo Implement test_check().
    */
    public function test_check() {
        throw new PHPUnit2_Framework_IncompleteTestError;
    }
}
if (PHPUnit2_MAIN_METHOD == "CartTest::main") {
    CartTest::main();
}
?>

TestDoxの生成

次にTestDoxの生成についてです。恥ずかしながら今回初めて知ったのですが、テストケースのメソッド名からドキュメントを生成するツールでアジャイル系開発手法で使われているようです。参考までに、JUnit用のTestDoxについては以下のサイトで公開されています。

手順としては「--testdox-text」パラメータと出力するファイルを指定してテストクラスを実行させます。

●TestDoxの作成
$ /usr/local/lib/php5/bin/phpunit --testdox-text CartTest.testdox.txt CartTest
PHPUnit 2.2.0 by Sebastian Bergmann.
......
Time: 0.0053179264068604
OK (6 tests)
$ 
●生成されたTestDox - CartTest.testdox.txt
Cart
 - Init
 - Add positive
 - Add negative
 - Remove positive
 - Remove negative
 - Clear

また、「--testdox-html」パラメータの場合はHTML形式で出力されます。このHTMLには<html>タグや<body>タグが含まれていませんので、複数のテストを実行後、一つのファイルにまとめる、などバッチ的にも使えるかも知れません(Suiteを使えば済む話かも知れませんが。。。)。

コードカバレッジのレポート生成

最後はコードカバレッジのレポート生成についてです。以前紹介したXDebugのコードカバレッジAPIを使っているため、別途Xdebugをインストールする必要があります。今回は、2005/04/24時点の最新版であるXdebug2.0.0 beta2を使用しました。ざっくりないんストール手順は以下の通りです。

●Xdebug2のインストール
$ tar zxf xdebug-2.0.0beta2.tgz
$ cd xdebug-2.0.0beta2/
$ phpize
$ ./configure --enable-xdebug
$ make
$ su -
# make install
# vi /path/to/php.ini(extensionとしてxdebug.soを追加する)
# 

実行手順ですが、「--coverage-html」パラメータと出力するファイルを指定してテストクラスを実行させます。

●カバレッジレポートの作成
$ /usr/local/lib/php5/bin/phpunit --coverage-html CartTest.coverage.html CartTest
PHP Warning:  Xdebug MUST be loaded as a Zend extension in Unknown on line 0
PHPUnit 2.2.0 by Sebastian Bergmann.
......
Time: 0.045461893081665
OK (6 tests)
$ 

生成されたレポートはこちらですが、CSSファイルを別途用意することではじめて色分けが行われます。このCSSファイルはレポート用のCSSが用意されていないため、[PEARディレクトリ]/PHPUnit2/Extensions/CodeCoverage/Renderer/HTML.phpの先頭コメントにあるCSSのサンプルを元にcodecoverage.cssとして作成したものです。なお、TestDocと同様「--coverage-text」パラメータもあります。

まとめ

こう見てみると、PHPにもだいぶこういったツールが揃ってきましたね。今後が楽しみです(^-^)



About This Site |  Privacy Policy |  Contact
Copyright © 1999 - 2005 by Hideyuki SHIMOOKA all rights reserved.