Espérance という BDD っぽくアサーションを行うライブラリを書いています。
とりあえず PHPUnit の中で使ってみるイメージはこんな感じです。

あと、前に中途半端に作った xSpec フレームワーク Speciphy と組み合わせるとこんなイメージにもできそうです。 (まだ作ってませんが)

なんで should でなく expect なのか

RSpec 3 からは expect がデフォルトになる、という記事が先日ありました。

RSpec’s New Expectation Syntax

RSpec といえば “foo”.should == “foo” といった具合に、全てのオブジェクトに should メソッドを生やしての アサーション (エクスペクステーション) が特徴でしたが、やっぱり Kernel モジュールに勝手にオブジェクト足されるのは気持ち悪いよね、ということで古き良き Test::Unit を使い続ける人も一定数いるように思われます。

should と比較して、expect は RSpec コンテキスト (多分 ExampleGroup とか Example とかの中だろうけど特に読んではいません) の中に存在するメソッドなのでグローバル空間に副作用がありませんし、それでいて英語としても読み下しやすい DSL だと言えます。

そして expect といえば、JavaScript の BDD ライブラリでは結構ベタだったりします。
Jasmine もそうですし、Socket.IO の作者による expect.js というものもあります。
Espérance は expect.js を PHP に移植して作っています。

PHPSpec について

PHP はオープンクラスでないので、全てのオブジェクトに should メソッドを生やす、なんてことはできません。

そこで PHPSpec では $this->spec(“foo”) とすることで、値を Interceptor オブジェクトでラップし、Interceptor がマジカルな DSL を実現する、という作りになっています。

PHPSpec の Interceptor は独立したライブラリではありませんが、Speciphy でも拝借しており、独立性の高いモジュールでもあります。

PHPSpec の Interceptor は凄いんですが、正直言って頑張り過ぎなんではないかと思っています。

PHPSpec 本体の spec を見ていると $this->matcher->getDescription()->should->be(‘be equal to 1′); なんてことをしていたりします。

$this->expect($this->matcher->getDescription())->to->be(‘be equal to 1′); とかで充分やん、とか思ってしまうわけです。

とはいえ Espérance も __get とか __call とか使ってますし、マジカルであることに変わりはないのですが、作りは大分シンプルになっていると思います。
expect.js の実装を真似ているので、expect.js が凄いというだけの話でもあるのですが。

expect.js の DSL の実装について

ところで expect.js では expect(1).to.be(1) といったプロパティのチェーンを、Assertion オブジェクトを再帰的に生成して to プロパティに新しい Assertion を差し込む、なんてことをしており、一度 expect(1) としただけで 40 数回ぐらいコンストラクタが呼ばれるています。

PHP では __call でプロパティ読み出しをオーバーライドすることができるので、Espérance では再帰的なコンストラクタは行われません。

JavaScript でも最近の処理形だと Object.defineProperty() とかで似たようなことができるような気がするのですが、それをしないのはより幅広い環境での使えるようにということなんでしょうか。 (JavaScript あんまり知らないのでよくわかりません)

名前について

Google 翻訳に expectation と入力してフランス語として出てきた単語から適当に選びました。

フランス語を名前として使うアイディアは、最近話題になった React (通称 node.php) でも使われているイベントディスパッチャライブラリ Événement のパクリです。

今後について

Speciphy もそうですが、ボクはライブラリやフレームワークを作ってもメンテが続かないタイプの人間だということだけ書いておきます。

でもまぁこういう感じでアサーションだけがライブラリと独立しているのは割とありなんじゃないかなーとは思いますし、Packagist への登録ぐらいはやっておきたいと思っています。思っているだけですが。

というわけでレバ刺しを食べに行きます。

, ,

この記事は個人的なメモです.

[エラー: isbn:4320026926:n というアイテムは見つかりませんでした]を読んでいて, 構造体の章に出てきた BinaryTree がよくわからないので, とりあえず Ruby で実装してみた.
単語をキーに, その回数を数えるだけの単純なプログラム.

BinaryTree::Node クラス

ツリー構造のノードを表すクラス.
単語, 回数, そして左/右のノードをプロパティとして持ちます.

RSpec

せっかくなので BDD (振る舞い駆動開発) の練習.

実行するとこのようになります.

$ rspec binarytree_spec.rb --format doc
BinaryTree::Node
  with word "foo" and no children
    it should behave like Node with word "foo"
      word
        should == "foo"
    it should behave like Node with no children
      left
        should be nil
      right
        should be nil
    size
      should == 1
    count
      should == 1
    words
      should == ["foo"]
    count_all
      should == 1
  with word "foo" and inserted a node with word "bar"
    it should behave like Node with word "foo"
      word
        should == "foo"
    it should behave like Node with one child with no children
      size
        should == 2
    right
      should be nil
    words
      should == ["bar", "foo"]
    count_all
      should == 2
    its child on the left
      it should behave like Node with no children
        left
          should be nil
        right
          should be nil
      word
        should == "bar"
      size
        should == 1
      count
        should == 1
      count_all
        should == 1
  with word "foo" and inserted a node with word "hoge"
    it should behave like Node with word "foo"
      word
        should == "foo"
    it should behave like Node with one child with no children
      size
        should == 2
    left
      should be nil
    words
      should == ["foo", "hoge"]
    count_all
      should == 2
    its child on the right
      it should behave like Node with no children
        left
          should be nil
        right
          should be nil
      word
        should == "hoge"
      size
        should == 1
      count
        should == 1
      count_all
        should == 1
  with word "foo" and inserted a node with word "foo"
    it should behave like Node with word "foo"
      word
        should == "foo"
    it should behave like Node with no children
      left
        should be nil
      right
        should be nil
    count
      should == 2
    words
      should == ["foo"]
    count_all
      should == 2
  with word "foo" and inserted a node with word "bar" 3 times
    it should behave like Node with word "foo"
      word
        should == "foo"
    right
      should be nil
    count
      should == 1
    size
      should == 2
    words
      should == ["bar", "foo"]
    count_all
      should == 4
    its child on the left
      word
        should == "bar"
      size
        should == 1
      count
        should == 3
      count_all
        should == 3
  with word "foo" and inserted a node with word "hoge" 3 times
    it should behave like Node with word "foo"
      word
        should == "foo"
    left
      should be nil
    count
      should == 1
    size
      should == 2
    words
      should == ["foo", "hoge"]
    count_all
      should == 4
    its child on the right
      word
        should == "hoge"
      size
        should == 1
      count
        should == 3
      count_all
        should == 3
  with word "a" and inserted nodes with word "b" ~ "j"
    size
      should == 10
    words
      should == ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]

Finished in 0.03146 seconds
57 examples, 0 failures

いやー, RSpec って楽しいですね.
PHP にもこういうの欲しい.

実行してみる

“to be or not to be” というフレーズ中に含まれる単語と, その回数を計算します.
結果は以下のとおり.

$ ruby example.rb
be (2)
not (1)
or (1)
to (2)

4 words.
6 nodes.

今度は C/C++ でも実装してみよう.
そして BTree や B+Tree についても勉強してみよう.

というわけで今日はここまで.

, ,