kahlanを使ってみる

# kahlanを使ってみる

## 動機

- PHPでもsubtestを書けるフレームワークが使いたい
    - subtestが書けるとテスト対象のコードの構造をテストに再現しやすい
    - [公式サイト](https://kahlan.github.io/docs/getting-started.html)  
 
## 導入

- composerでインストール

```
$ composer require --dev kahlan/kahlan
Using version ^4.0 for kahlan/kahlan
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing kahlan/kahlan (4.0.6): Downloading (100%)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
```

## 使ってみる

- テスト対象のコードを用意する 
    - フラグを受け取って加算/減算をするだけの簡単なコード

```php
<?php

namespace App\Services;

class SomeService
{
    /**
     * フラグが立っていれば減算、未指定/フラグが立っていない場合は加算する
     *
     * @param  int          $number
     * @param  bool         $is_decrement
     * @return int
     */
    public function incrementOrDecrement(int $number, bool $is_decrement = false): int
    {
        if ($this->isDecrement($is_decrement)) {
            // 減算した結果がマイナスになる場合は0にする
            if ($number > 1) {
                $number = $number - 1;
            } else {
                $number = 0;
            }
        } else {
            $number = $number + 1;
        }

        return $number;
    }

    /**
     * @param  bool         $is_decrement
     * @return bool
     */
    private function isDecrement(bool $is_decrement): bool
    {
        return $is_decrement;
    }
}
```

- テストケースを作成する
    - rspecライクにdescrive/itを使って記述できる
    - モックも書けるので使い勝手はよさそう

```php
<?php

describe('incrementOrDecrement', function() {
    beforeAll(function() {
        $this->service = new App\Services\SomeService();
    });

    describe('減算', function() {
        describe('減算結果が0以上', function() {
            it ('減算結果が1', function() {
                expect($this->service->incrementOrDecrement(2, true))->toBe(1);
            });
            it ('減算結果が0(境界値)', function() {
                expect($this->service->incrementOrDecrement(1, true))->toBe(0);
            });
        });
        describe('減算結果が0以上', function() {
            it ('減算結果がマイナス', function() {
                expect($this->service->incrementOrDecrement(-1, true))->toBe(0);
            });
        });
    });

    describe('加算', function() {
        it ('第2引数なし', function() {
            expect($this->service->incrementOrDecrement(1))->toBe(2);
        });
        it ('第2引数あり', function() {
            expect($this->service->incrementOrDecrement(2, false))->toBe(3);
        });
        it ('モックもあるらしいので使ってみる', function() {
            allow($this->service)->toReceive('isDecrement')->andReturn(true);
            expect($this->service->incrementOrDecrement(3, false))->toBe(2);
        });
    });
});
```

## 実行

- ファイル名はrspecライクに`*.spec.php`という名前にする必要がある
- `--spec`オプションに実行したいファイルのあるディレクトリを指定する

```
$ ./vendor/bin/kahlan --spec=tests/Unit/Services/
            _     _
  /\ /\__ _| |__ | | __ _ _ __
 / //_/ _` | '_ \| |/ _` | '_ \
/ __ \ (_| | | | | | (_| | | | |
\/  \/\__,_|_| |_|_|\__,_|_| |_|

The PHP Test Framework for Freedom, Truth and Justice.

src directory  :
spec directory : /path/to/tests/Unit/Services

......                                                              6 / 6 (100%)



Expectations   : 6 Executed
Specifications : 0 Pending, 0 Excluded, 0 Skipped

Passed 6 of 6 PASS in 0.035 seconds (using 3MB)
```

## 使ってみた感想

- subtestはPerlのTest::Moreで慣れていたので、ないと不便に感じることが多かった
- kahlanはrspec経験者にも使いやすいテストフレームワークだと思う
- モックを使ってみたけど、privateメソッドでも簡単に偽装できるのが便利
- 今後はkahlanを使うように啓蒙したいと思う
- 今回はLaravelのデフォルトなtestsディレクトリを使ったけど、specディレクトリを新たに切って住み分けもできるようにしておく方がよいかな