PHPSpec では、振る舞いを表すサンプルを書く際に専用のドメイン特化言語 (DSL) を使用します。この DSL はできるだけ自然な (かつ文法的に正しい) 英語に近い形式で書けるように作られており、 直感的に使用することができます。また、読んで理解するのも簡単になります。
DSL の基本的な形式は、Expectation (should あるいは should not) と Matcher (be, beAnInstanceOf, equal, etc.) を用意して、それを新規スペックに渡した値やオブジェクトに関連づけるというものです。 こうすることで、比較的読みやすい文章ができあがります。 ほんの少し手を加えるだけで、普通の英語 (あるいはその他の言語!) に変換することができます。 変換の手間が最小限であること、そして私たちが実際に頭で考える内容に近いこと などから、スペックの内容をレビューしたり修正したりするのも常に簡単です。
例 5.5. スペック DSL の例: Bowling は Logger のインスタンスであってはならない
$bowling = new Bowling;
$this->spec($bowling)->shouldNot->beAnInstanceOf('Logger');
PHPSpec のサンプルメソッドで DSL のインスタンスを作成するには、
PHPSpec_Context::spec() を使用します。
このメソッドには、次の 3 種類のパラメータを渡すことができます。
スカラー値 (文字列、整数値、論理値、浮動小数点数値、あるいは配列)
オブジェクト
オブジェクトの名前とコンストラクタへのパラメータ
例 5.6. Actual Term: スカラーの例
$this->spec('i am a string')->should-beString();
$this->spec(567)->should->equal(567);
$this->spec(array(1, 2, 3))->shouldNot->beEmpty();
例 5.7. Actual Term: オブジェクトの例
$this->spec(new Bowling)->should->beAnInstanceOf('Bowling');
$bowling = new Bowling;
$this->spec($bowling)->shouldNot->havePlayers();
例 5.8. Actual Term: オブジェクト名とコンストラクタのパラメータの例
$this->spec('Bowling', new Player('Joe'), new Player('Jim'))->should->havePlayers();
英語と同様、あらゆる期待は大きく二つに分類できます。
失敗することを期待するものと、成功することを期待するものです。
実際の値が一致してほしいのか一致してほしくないのかに応じて、
DSL で should あるいは
shouldNot のいずれかを使用します。
以下のサンプルは、どれも成功するはずです。
例 5.9. Expectation Term: さまざまなサンプル
$spec->( array() )->should->beEmpty();
$spec->('Bowling')->shouldNot->havePlayers();
$spec->('i am a string')->should->match("/^[a-z ]$/");
$spec->(is_int('string'))->shouldNot->beTrue();
ユニットテストのフレームワークがアサーション (表明) に頼っているのに対して、
PHPSpec は期待 (Expectation Term) と条件 (Matcher) に責任を分担させています。
Matcher はシンプルなオブジェクトで、実際の値と期待内容を
DSL のメソッドで比較します。そしてマッチしたか否かを返します。
Matcher の形式は PHPSpec_Matcher_Interface
インターフェイスで定義されているので、独自の Matchers
を書くこともできます (現在この機能は未完成です)。
PHPSpec フレームワークには、すでにさまざまな Matcher が用意されています [注意: 中にはまだ開発途中のものもあります]。
Matcher とは、一般にスペックの最後に追加されるものです。 先ほどごらんいただいた例でもそのようになっています。
すべての Matcher は、boolean 値を返します。
したがって、スペックを記述する「流れるようなインターフェイス」
においては一番最後にコールすることになります。
NULL とされているパラメータは、
通常は不要であることを意味します
(Matcher の名前から、期待する内容は暗黙のうちに決まります)。
表 5.1. PHPSpec の Matcher
| Matcher メソッド | 説明 |
|---|---|
|
|
equal() と同じ意味で、
英語っぽく書くために用意されています。
|
bool beEqualTo (mixed
$expected)
|
equal() と同じ意味で、
英語っぽく書くために用意されています。
|
bool equal (mixed
$expected)
|
期待する内容と等しいかどうかを調べます。 スカラー値、オブジェクトのクラス、配列の内容など、 種類に応じて適切な比較を行います。 |
bool beTrue (null
$expected)
|
実際の値を TRUE と比較します。
|
bool beFalse (null
$expected)
|
実際の値を FALSE と比較します。
|
bool beNull (null
$expected)
|
実際の値が NULL かどうかを調べます。
|
bool beEmpty (mixed
$expected)
|
実際の値が空かどうかを調べます (empty() を使用します)。
|
|
|
実際の値が設定されているかどうかを調べます (isset()
を使用します)。
|
|
|
実際の値がオブジェクトであり、かつ指定したクラスのインスタンスであるかどうかを調べます。 |
|
|
実際の値の型が、文字列で指定した型 ('int'、'stdClass' など) と一致するかどうかを調べます。 |
|
|
実際の値が整数値かどうかを調べます。 厳格なチェックを行うので、数値形式の文字列は整数値と見なされません。 |
|
|
実際の値が配列かどうかを調べます。 |
|
|
実際の値が文字列かどうかを調べます。 |
|
|
実際の値が浮動小数点数値かどうかを調べます。 |
|
|
実際の値がオブジェクトかどうかを調べます。 どのようなクラスのオブジェクトなのかは調べません。 |
|
|
実際の値が指定した値より大きい
(>) かどうかを調べます。
|
|
|
実際の値が指定した値より小さい
(<) かどうかを調べます。
|
|
|
実際の値が指定した値以上
(>=) かどうかを調べます。
|
|
|
実際の値が指定した値以下
(<=) かどうかを調べます。
|
叙述型の Matcher
とは、指定したオブジェクトから実際の値を取得する Matcher です。
オブジェクトを調べ、isSomething()
あるいは hasSomething()
形式のメソッドをコールします。
すでに先ほどの DSL の例でこれを使用しており、
DSL のメソッド havePlayers()
は Bowling::hasPlayers()
メソッドをコールします。コールしたメソッドの戻り値である boolean
値が TRUE かどうかを調べ、
その結果を返します。
例 5.10. クラスと叙述型の Matcher のコール例
class Insect { // Insect ... 昆虫
public function isInsect() { // ……は昆虫です
return true;
}
public function hasWings() { // ……は羽根を持っています
return true;
}
}
class Flea extends Insect { // Flea ... ノミ
public function hasWings() {
return false; // ノミには羽根がありません
}
}
class DescribeFlea extends PHPSpec_Context {
public function itShouldBeAnInsect()
{
$flee = new Flea;
$this->spec($flea)->should->beAnInsect(); // Flea::isInsect() == TRUE
}
public function itShouldHaveNoWings()
{
$flee = new Flea;
$this->spec($flea)->shouldNot->haveWings(); // Flea::hasWings() == FALSE
}
}
叙述型の Matcher メソッドとして、DSL 内では
be()、beA()、
beAn() 形式を使用することができます。
これらはそれぞれ文法的に正しくなるように区別されているだけであり、
それ以外はまったく同じです。have(), haveA() および haveAn()
も同様です。同じような規則でオブジェクトのメソッドを探します
(オブジェクトのメソッド名が文法的に正しいものである必要があります!)。
将来的には、他の形式にもこのような叙述型を取り入れる予定です。
何かよい案があれば、ぜひ教えてください。