|
zip形式
tgz形式
gihyo.jpにPHPUnit3で始めるユニットテストというタイトルで記事を書きました。PHP5をお使いの方は、そちらを参照してください。
ここにある情報はかなり古くなっており、正しくなくなっている可能性があります。掲載しているサンプルコードiなどは、最新のPHPでは動作しない、もしくは、別途設定・調整が必要になるかも知れません。情報を鵜呑みにせず、あなたの手を動かして、あなたの目で確認してください。
PhpUnitは一時期PEARに登録されていましたが、現在は別プロジェクトとして活動しています。新しいURLはhttp://phpunit.de/です。
テスト。。。ああ、なんてイヤな響きでしょう。。。(^-^;
「テストすること」はプログラムの品質を保証するということで非常に重要な作業だ、ということは百も承知と思いますが、コーディングと比べてやはり「イヤ」なものですねぇ。面倒だし、自分のプログラムの欠点を探すので面白くないし。。。また、リリースしたプログラムの修正が必要かどうか、という状態になったとき、「また再テストかぁ?」ってなりませんか?
「面倒なテストを自動化できたら。。。」ってことを実現するツール、それがPhpUnitです(何か、深夜のTVショッピングのノリだ。。。)。
最近話題になっているXP(Windowsではありません。「eXtreme Programing」と呼ばれる開発手法)で提唱されている12のプラクティス(実践すべき項目)の中で「テスティング」「リファクタリング」で必要となる「自動テストを行うための環境」を構築するためのツールの1つになります。JavaではJUnit、VBではVBUnit、C++ではCppUnitといった具合に、各言語についていろいろなテストツールがあります(Palm Unitってのもあるんですね。。。)。
PhpUnitでカバーできるテストは、あくまで「ユニットテスト」になります。つまり、関数やクラス単位のテストです。今回は、ショッピングカートクラスを対象に考えてみました。
まずは、PhpUnitの入手とインストールです。PhpUnitは、PhpUnit本家(http://sourceforge.net/projects/phpunit/)から入手することができます。2002/03/29現在、最新版はPhpUnit0.3です。PhpUnit本家から入手したtgzは、GNU tarなどで適当なディレクトリ(できれば、include_pathに指定されたディレクトリ)に展開すれば、インストール完了です。
まずは、作成するクラスの仕様を決めます。今回は、ショッピングカートを考えていますので、以下のようなメソッドを定義しました。ここで重要なことは、具体的な実装を書かない事です。
●Cart.phl
<?php
class Cart
{
function Cart()
{
}
function add($item_cd, $amount)
{
}
function remove($item_cd, $amount)
{
}
function clear()
{
}
function getAmount($item_cd)
{
}
}
?>
次にテスト用プログラムを作成します。PhpUnitでは、TestCaseクラス(phpunit.phpファイル内で定義)のサブクラスとして作成します。今回は、add、removeの引数amountに負の数が指定された場合なにもしない、ということにしてテストプログラムを作成しています。
また、テスト用プログラムには、できるだけコメントを書くようにした方が良いようです。まあ、書いておかないと、あとで「これって何のテストやってるんだっけ?」になりそうですしね。
●CartTest.php
<?php
require_once("phpunit.php");
require_once("Cart.phl");
?>
<?php
class CartTest extends TestCase
{
var $cart_;
function CartTest($name)
{
$this->TestCase($name);
}
function setUp()
{
$this->cart_ = new Cart();
}
function tearDown() {}
function testInit()
{
$this->assertEquals(0, $this->cart_->getAmount("aaa"));
}
function testAddPositive()
{
$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"));
}
function testAddNegative()
{
$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"));
}
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"));
}
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"));
}
function testClear()
{
$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"));
}
}
$ts = new TestSuite("CartTest");
$tr = new TestRunner();
$tr->run($ts);
?>
テストプログラムを作成したらブラウザからアクセスし、テストに失敗することを確認しておきます。これは、正しい実装がないとエラーになることを確認するためです。実行結果はこんな感じです。
テストプログラムの作成・確認が終わったら、Cartクラスの実装を行います。以下がこの時点でのコードで、全てのテストをパスしています。実際には、コーディングとテストを交互に行い、最終的に全てのテストをパスするまでこれを繰り返しました。
実行結果はこうなります。
●Cart.php
<?php
class Cart
{
var $items_;
function Cart()
{
$this->items_ = array();
}
function add($item_cd, $amount)
{
if (!isset($this->items_[$item_cd])) {
$this->items_[$item_cd] = 0;
}
if ($amount > 0) {
$this->items_[$item_cd] += $amount;
}
}
function remove($item_cd, $amount)
{
if (!isset($this->items_[$item_cd])) {
$this->items_[$item_cd] = 0;
}
if ($amount > 0) {
$this->items_[$item_cd] -= $amount;
if ($this->items_[$item_cd] < 0) {
$this->items_[$item_cd] = 0;
}
}
}
function clear()
{
unset($this->items_);
}
function getAmount($item_cd)
{
if (!isset($this->items_[$item_cd])) {
$this->items_[$item_cd] = 0;
}
return $this->items_[$item_cd];
}
}
?>
ここで、コードを修正してみます。add、remove、getAmountの各メソッドに同じコードがありますので、これを_checkというprivateメソッドにまとめ、それそれ呼び出すことにします。以下が修正後のコードです。
●Cart.php
<?php
class Cart
{
var $items_;
function Cart()
{
$this->items_ = array();
}
function add($item_cd, $amount)
{
$this->_check($item_cd);
if ($amount > 0) {
$this->items_[$item_cd] += $amount;
}
}
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;
}
}
}
function clear()
{
unset($this->items_);
}
function getAmount($item_cd)
{
$this->_check($item_cd);
return $this->items_[$item_cd];
}
function _check($item_cd)
{
if (!isset($this->items_[$item_cd])) {
$this->items_[$item_cd] = 0;
}
}
}
?>
修正が終わったら、またテストします。全てのテストにパスすれば、めでたく修正完了となります。上のコードは全てのテストにパスしています。
テストが自動化されていますので、今までのような「修正をするとテストが面倒」ということはないですね。API(メソッドのシグネチャ)を変えない限り、全く違う実装でも全てのテストをパスする限り、動作が保証されます。これって、開発者にとって非常に強力な(楽するための)味方ではないでしょうか?
|