type holyshared = Engineer<mixed>

技術的なことなど色々

Hacklang用のテスティングフレームワークを作った件

Screen Shot

hhspecifyっていうBDD styleのテスティングフレームワークを公開しました。 下記のコマンドでcomposerを利用してインストールできます。

composer require hhspecify/hhspecify

使い方は下記の通り。

設定ファイルの作成

まず、最初に設定ファイルを作成します。
ファイル名はhhspecify.hhです。

configureメソッドにConfigBuilderを受け付ける、ラムダ式を指定します。 今の所設定できるのが、パッケージとレポーターだけです。

<?hh //partial

use hhspecify\HHSpecify;
use hhspecify\config\ConfigBuilder;
use hhspecify\reporter\SpecificationReporter;

HHSpecify::configure((ConfigBuilder $builder) ==> {

    $package = shape(
        'namespace' => 'vendorname\\spec\\', //スペックファイルの名前空間
        'packageDirectory' => __DIR__ . '/spec' //スペックファイルのディレクトリ
    );

    $builder->package($package)
        ->featureReporter(new SpecificationReporter());

});

スペックファイルの作成

スペックファイルを置くディレクトリにスペックファイルを置きます。
ファイル名はなんでもOKです。

ここではStackSpecというファイル名にします。
implementsのインターフェース指定にSpecificationを指定します。

use hhspecify\Specification;
use hhspecify\feature\FeatureVerifierBuilder as Feature;

final class StackSpec implements Specification
{

    public function __construct(
        private Vector<int> $stack = Vector {}
    )
    {
    }

}

テストメソッドの記述

テストメソッドにFeature属性を指定します。
引数のFeatureVerifierBuilderインスタンスを実行時に受け取るので、 setup/when/then/cleanupの順に検証コードを記述していきます。

  • setup - 初期処理のコードを記述する
  • when - テストをしたい処理をするコードを記述する
  • then - whenを実行した後の結果を検証するコードを記述する
  • cleanup - 後処理のコードを記述する
<<Feature("Vector::add")>>
public function add_value_to_vector(Feature $feature) : void
{
    //setup block - Setup
    $feature->setup(() ==> {
        $this->stack = Vector {}; //Vectorを初期化する
    });

    //when block - Stimulus
    $feature->when(() ==> {
        $this->stack->add(1); //値を追加する
    });

    //then block - Response
    $feature->then(() ==> {
        invariant($this->stack->count() === 1, 'must have been added value'); //値が追加されているか検証する
    });
}

スペックファイルの実行

後は下記のコマンドで、実行するだけです。

vendor/bin/hhspecify

Expectationを再設計した話。

expectationを開発していて、ちょっと気に入らない箇所がいくつか出てきたので、再設計してみた。

再設計したので、expectationはもうメンテしておらず、 expectに変わったので、注意してください。 また、peridotプラグインも、peridot-expect-pluginに変わり、再設計した方のものに変わっています。

再設計後のマッチャーAPIは互換性担保しているはず。
設計を見直した点は、下記の通り。

  1. アノテーションを使用するメリットがそんなになかった。

    旧ロジックはアノテーションでMatcherのどのメソッドを使用して、評価するかを指定する設計だった。 こんな感じで、アノテーションで指定する。

     ```php
     /**
      * @Lookup(name="toEqual")
      * @Lookup(name="toBe")
      * @param mixed $actual
      */
     public function match($actual)
     {
         $this->setActualValue($actual);
         return $this->getExpectValue() === $this->getActualValue();
     }
    
     /**
      * @Lookup(name="toBeTrue")
      */
     public function matchTrue($actual)
     {
         return $this->setExpectValue(true)->match($actual);
     }
     ```
    

    アノテーションエイリアス割り当てられるくらいのメリットぐらいしかなかったので、使わないようにした。

  2. Matcherのソースコードに対する、対応付けがわかりににくい。

    Matcherをソースコードと1対1になるようにした。

    • 例: toBeTrue -> ToBeTrue.php
    • 例: toBeEqual -> ToBeEqual.php
  3. エラーメッセージがわかりにくい。

    rspec-expectationsgomegaあたりが、どうしているか参考にした。

Peridotのプラグインを使用していた人へ

移行はそんな難しくなくて、peridot.phpを編集して、ExpectationPluginの部分をExpectPluginにします。 基本的にはこれでOKなはず。

use expect\peridot\ExpectPlugin;

return function(EventEmitterInterface $emitter) {
    ExpectPlugin::create()->registerTo($emitter);
};

あとはspec書くだけ。

describe('Example', function() {
    describe('#create', function() {
        it('return new Example instance', function() {
            expect(Example::create())->toBeAnInstanceOf('Example');
        });
    });
});

まとめ

いつから俺は、be_truthy/be_falseyの仕様を勘違いしていたのだ

PHPexpectationライブラリ作っているのですが、思いっきり間違った実装をしていることに、rspecのリファレンスを見ていて、気がつきました。

matcherのtoBeTruthy/toBeFalseyが、rspecのmatcherでは、

  • be_truthy - nil、若しくはfalseでない
  • be_falsey - nil、若しくはfalse

なんですね。 自分はずっと、be_true / be_falseのエイリアスだと思っていた...。

リファレンスをみるとちゃんと書いてある。

    expect(actual).to be_truthy    # passes if actual is truthy (not nil or false)
    expect(actual).to be_falsey    # passes if actual is falsy (nil or false)

というわけで、バージョン1.4.3で修正しました。
古いバージョンでは、toBeTruthy/toBeFalseyが、toBeTrue/toBeFalseと等価の振る舞いになっています。