2009.10.15
PHPUnitを使ってみました
筆者は最近PHPを使い始めました。
PHPは型キャストをしなくとも文字列と整数などを勝手にキャストしてくれるなど、型をあまり気にせずにプログラムを書いても、なんとなく動いてくれます。
もちろん変数の型宣言もなし、です。
こういった動的な、弱い型付けを持つ言語では、キャストなどに必要なコードの量を減らすことができる反面、プログラマの意図しない変換が行われてしまうことがあり、原因のわかりづらいバグを生み出しがちです。
静的であれ、動的であれ、コンパイラのチェック(と制約)が弱いとき、バグを減らすために重要なのは、プログラムを小さな単位に分解し、それぞれの動作を検証することができるようにすることです。
そうすると、プログラムが動かないとき、テストを使ったバグの原因推定を行いやすくなるからです。
というわけでユニットテストのツールを導入してみました。
今回は、ググってみて有名そうだったPHPUnit(PEAR版)[1]を使います。
pearのバージョンが古いとうまくいかない場合があるのでそのときは最新のpearにアップデートしてください。
#pear install phpunit/PHPUnit
を実行するとインストール完了。
phpunitコマンドが実行可能であることを確認してください。
ところで、PHPUnitに限らず、テストドリブン開発の
基本的な流れは以下のとおりです。
- 目的のプログラムに必要なメソッド、関数を検討して列挙する。
- メソッド名、関数名のみが定義されたもの(ここではスケルトンと呼びます)を作成する。
- テスト・コードを作成する(テストコードを読むと、メソッドや関数の仕様がわかる)。
- スケルトンの中身を実装してテストをする。
- テストをすべてパスするまで、実装とテストを繰り返す。
スケルトンは、Javaでいうところのインタフェース定義に近いですね。
筆者もPHPUnitを使って実際に試してみました。
今回作るのは、こんなクラス(Value.php)です。
$value = new Value();
echo $value -> set(3)-> plus(1) -> minus(2)-> is();
実行結果
2
set, plus,minusなどの計算をするたびに自分自身の参照を戻して、
1行で複数の処理を行うことができるようにします。
スケルトンを書くとこんな感じになります。
<?php
class Value{
public function set($val){}
public function is(){}
public function plus($val){}
public function minus($val){}
}
?>
Value.php
ここでテストを書き、仕様を明確にします。
コマンドラインでテストを行うためには、PHPUnit_Framework_TestCaseを拡張したテストケースを
作成します。
testA, testBなど、名前がtestで始まるメソッドそれぞれがテストとして扱われます。
それぞれのテストの実行前にはsetUpメソッドがよばれ、
実行後にはtearDownメソッドが呼ばれます。
<?php
require_once("PHPUnit.php");
require_once("Value_skel.php");
class ValueTest extends PHPUnit_Framework_TestCase{
var $value;
public function setUp(){
$this -> value = new Value();
}
public function tearDown(){}
public function testSet(){
$obj = $this -> value -> set(1);
$this -> assertType("Value", $obj);
$obj = $this -> value -> set(5);
$this -> assertType("Value", $obj);
$obj = $this -> value -> set(-10);
$this -> assertType("Value", $obj);
try{
$this -> value -> set("hoge");
$this -> fail("invalid argument");
}catch(Exception $e){}
}
public function testIs(){
try{
$this -> value -> is();
$this -> fail();
}catch(Exception $e){}
$v = $this -> value -> set(1) -> is();
$this -> assertEquals(1, $v);
$v = $this -> value -> set(5) -> is();
$this -> assertEquals(5, $v);
$v = $this -> value -> set(-10) -> is();
$this -> assertEquals(-10, $v);
}
public function testPlus(){
try{
$this -> value -> plus(3);
$this -> fail();
}catch(Exception $e){}
$v = $this -> value -> set(1) -> plus(11) -> is();
$this -> assertEquals(12, $v);
$v = $this -> value -> set(1) -> plus(-10) -> is();
$this -> assertEquals(-9, $v);
try{
$v = $this -> value -> set(1) -> plus("moge") -> is();
$this -> fail();
}catch(Exception $e){
}
}
public function testMinus(){
try{
$this -> value -> minus(3);
$this -> fail();
}catch(Exception $e){}
$v = $this -> value -> set(1) -> minus(11) -> is();
$this -> assertEquals(-10, $v);
$v = $this -> value -> set(1) -> minus(-10) -> is();
$this -> assertEquals(11, $v);
try{
$v = $this -> value -> set(1) -> plus("moge") -> is();
$this -> fail();
}catch(Exception $e){}
}
}
?>
ValueTest.php
PHPUnit_Framework_TestCaseクラスに組み込まれた各種assert関数を使い、戻り値の型をチェックしたり(assertType)、期待する値が戻ってくるか(assertEquals)、
しかるべき場所では例外を送出するか、などをチェックしていきます。
テストを実行します。
phpunit ValueTest.php
ここまで書いてテストを実行してみると、Fatal Errorが出る箇所でテスト全体が止まってしまいました(その後に実行されるべき他のテストが実行されない)。
まあ、エラー箇所がわかるのでそこを修正していけば最終的には全体が完成するわけですが、明らかに未実装箇所のテストには、markTestSkippedなどのメソッドを使ってスキップしてやるほうが、一通りのテストを実行することができるので得策です。
完成したValueクラスは以下の通りです。
<?php
class Value{
var $value = null;
public function set($val){
$this -> value = $val;
$ref = &$this;
return $ref;
}
public function is(){
if($this -> value == null){
throw new Exception("value unset.");
}
return $this->value;
}
public function plus($val){
$this -> value += $val;
$ref = &$this;
return $ref;
}
public function minus($val){
$this -> value -= $val;
$ref = &$this;
return $ref;
}
}
?>
Value.php
ここでは使わなかったassertメソッドもたくさんあります。
公式ページもご覧ください。
参考文献:
筆者も、これを読んで勉強中です。
[1]PHPUnit http://www.phpunit.de/
Trackback URL
Comment & Trackback
Comment feed
Comment