PHPUnit3で始めるユニットテスト

第2回 ショッピングカートクラスを作ってみる(1)

この記事を読むのに必要な時間:およそ 7 分

2番目の仕様「商品を追加する場合,商品コードと数量(正数)を指定する」

次に,2番目の仕様

商品を追加する場合,商品コードと数量(正数)を指定する

です。この仕様に対するテストはどのようなものでしょうか?ここではまず以下の2つを考えてみます。

  • 追加するためのメソッドにコードと正数を渡すとtrueを返す
  • 追加するためのメソッドにコードと非正数(0と負の正数)を渡すと,例外を投げる

なお,投げられる例外は,SPL(Standard PHP Library)拡張モジュールで定義されている例外OutOfBoundsExceptionとします。それでは,テストケースにテストを追加します。

<?php
require_once 'PHPUnit/Framework.php';
require_once 'Cart.php';

class CartTest extends PHPUnit_Framework_TestCase
{
    public function testInitCart() {
        $cart = new Cart();
        $this->assertTrue(is_array($cart->getItems()));
        $this->assertEquals(0, count($cart->getItems()));
    }

    public function testAddPositive() {
        $cart = new Cart();
        $this->assertTrue($cart->add('001', 1));
    }

    public function testAddZero() {
        $cart = new Cart();
        try {
            $cart->add('001', 0);
        } catch (OutOfBoundsException $e) {
            return;
        }
        $this->fail();
    }

    public function testAddNegative() {
        $cart = new Cart();
        try {
            $cart->add('001', -1);
        } catch (OutOfBoundsException $e) {
            return;
        }
        $this->fail();
    }
}

ここで例外のテストについて若干説明します。PHPUnit3では例外をテストする方法として次の2つが用意されています。

  • PHPUnit_Extensions_ExceptionTestCaseを使用する
  • try/catch構文とfailメソッドを使用する

先のテストケースでは,testAddZeroメソッドとtestAddNegativeメソッドで例外のテストを行っており,2番目の方法を採っています。testXXXXメソッド内で使われているfailメソッドは「ボトルネックメソッド」と呼ばれ,テストが失敗した際PHPUnitによってコールされるメソッドです。テストに失敗してtry/catch文を通過した場合に,このfailメソッドを直接呼び出すことで「テストに失敗した」ことをPHPUnitに伝えています。また,階層構造を持った例外をテストする場合,意図した例外を正しく捕捉しているかどうか,十分注意してください。なお,PHPUnit_Extensions_ExceptionTestCaseの解説については,PHPUnitポケットガイドを参照してください。

それでは,テストを実行しましょう。

$ phpunit CartTest
PHPUnit 3.1.7 by Sebastian Bergmann.

.PHP Fatal error:  Call to undefined method Cart::add() in /home/shimooka/public_html/gihyo.jp/01.phpunit/CartTest.php on line 15

Fatal error: Call to undefined method Cart::add() in /home/shimooka/public_html/gihyo.jp/01.phpunit/CartTest.php on line 15
$ 

Fatal errorが発生しましたので,Cartクラスにaddメソッドを実装します。

<?php
class Cart
{
    public function getItems() {
        return array();
    }

    public function add($item_cd, $amount) {
    }
}
$ phpunit CartTest
PHPUnit 3.1.7 by Sebastian Bergmann.

.FFF

Time: 0 seconds

There were 3 failures:

1) testAddPositive(CartTest)
Failed asserting that <null> is true.
/home/shimooka/public_html/gihyo.jp/01.phpunit/CartTest.php:15

2) testAddZero(CartTest)
/home/shimooka/public_html/gihyo.jp/01.phpunit/CartTest.php:25

3) testAddNegative(CartTest)
/home/shimooka/public_html/gihyo.jp/01.phpunit/CartTest.php:35

FAILURES!
Tests: 4, Failures: 3.
$ 

新しいテストが正しく失敗する(変な表現ですが)ことを確認しましたので,addメソッドの実装を行いましょう。

<?php
class Cart
{
    public function getItems() {
        return array();
    }

    public function add($item_cd, $amount) {
        if ((int)$amount > 0) {
            return true;
        } else {
            throw new OutOfBoundsException('Invalid amount');
        }
    }
}
$ phpunit CartTest
PHPUnit 3.1.7 by Sebastian Bergmann.

....

Time: 0 seconds


OK (4 tests)
$ 

うまくいきました。しかし,これで本当に「正数」だけうまく通ると言えるでしょうか?数量に小数や文字列を渡された場合のテストも追加して確認しておきましょう。

<?php
require_once 'PHPUnit/Framework.php';
require_once 'Cart.php';

class CartTest extends PHPUnit_Framework_TestCase
{
    public function testInitCart() {
        $cart = new Cart();
        $this->assertTrue(is_array($cart->getItems()));
        $this->assertEquals(0, count($cart->getItems()));
    }

    public function testAddPositive() {
        $cart = new Cart();
        $this->assertTrue($cart->add('001', 1));
    }

    public function testAddZero() {
        $cart = new Cart();
        try {
            $cart->add('001', 0);
        } catch (OutOfBoundsException $e) {
            return;
        }
        $this->fail();
    }

    public function testAddNegative() {
        $cart = new Cart();
        try {
            $cart->add('001', -1);
        } catch (OutOfBoundsException $e) {
            return;
        }
        $this->fail();
    }

    public function testAddNotNumeric() {
        $cart = new Cart();
        try {
            $cart->add('001', 'string');
        } catch (OutOfBoundsException $e) {
            return;
        }
        $this->fail();
    }

    public function testAddFloat() {
        $cart = new Cart();
        try {
            $cart->add('001', 1.5);
        } catch (OutOfBoundsException $e) {
            return;
        }
        $this->fail();
    }
}
$ phpunit CartTest
PHPUnit 3.1.7 by Sebastian Bergmann.

.....F

Time: 0 seconds

There was 1 failure:

1) testAddFloat(CartTest)
/home/shimooka/public_html/gihyo.jp/01.phpunit/CartTest.php:55

FAILURES!
Tests: 6, Failures: 1.
$ 

小数のテストで失敗しました。addメソッドを修正し,数量のフォーマットをチェックすることにします。

<?php
class Cart
{
    public function getItems() {
        return array();
    }

    public function add($item_cd, $amount) {
        if (preg_match('/^\d+$/', $amount) && (int)$amount > 0) {
            return true;
        } else {
            throw new OutOfBoundsException('Invalid amount');
        }
    }
}
$ phpunit CartTest
PHPUnit 3.1.7 by Sebastian Bergmann.

......

Time: 0 seconds


OK (6 tests)
$ 

今度は大丈夫そうですね。これで2番目の仕様の実装完了としましょう。

著者プロフィール

下岡秀幸(しもおかひでゆき)

PHP関連の情報サイト「Do You PHP?」の管理人。PHP歴は長いが,あまり仕事で使ったことがないという噂がある。最近はα版のPEAR・PECLに手を出しては地雷を踏んでいることが多い。

URLhttp://www.doyouphp.jp/http://d.hatena.ne.jp/shimooka/