【Laravel】フリガナ入力のバリデーションのテストコード作成

会員登録や問い合わせフォームで、ユーザーにフリガナを入力してもらうことは多々あると思います。
その時にバリデーションを組むと思うのですが、きちんと全角カタカナ（もしくはひらがな）でしか入力出来ないか確認するのを手入力でやるのは手間だと思います。
ということで、この記事ではフリガナ入力のバリデーションのテストコードについて書いていきます。
（今回の記事は、読者がはじめてテストを書く人という想定で書きました）

|**PHPのバージョンなど、実行環境**|
|:--|
|PHP 7.1.14  |
|laravel 5.6 |
|PHPUnit 7.2.7  |
# 参考にしたQiitaの記事
これは、一部異なる箇所もありますが、だいたいは下記記事を参考に書いています。
@n_mogiさんありがとうございます、参考にさせていただきました。
[【Laravel】フォームリクエストバリデーションのテストコード作成](https://qiita.com/n_mogi/items/57a946205df2a69889c2)
# フォームとフォームリクエストバリデーション作成
以下のような入力欄があるとします。

```html:hoge.blade.php
<form method="POST" action="register">
    <div class="user_name">
        <label for="user_name">名前</label>
        <input type="text" class="form-control{{ $errors->has('user_name') ? ' is-invalid' : '' }}" name="user_name" value="{{ old('user_name') }}">
        @if ($errors->has('user_name'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('user_name') }}</strong>
            </span>
        @endif
    </div>

    <div class="user_name_kana">
        <label for="user_name_kana">フリガナ</label>
        <input type="text" class="form-control{{ $errors->has('user_name_kana') ? ' is-invalid' : '' }}" name="user_name_kana" value="{{ old('user_name_kana') }}">
        @if ($errors->has('user_name_kana'))
            <span class="invalid-feedback" role="alert">
                <strong>{{ $errors->first('user_name_kana') }}</strong>
            </span>
        @endif
    </div>

    <button type="submit">送信</button>
</form>
```
これに対してフォームリクエストバリデーションを組む時は、まず以下のコマンドをターミナルで入力します。

```bash
$ php artisan make:request FugaRequest
```
こうすると、フォームリクエストクラス（FugaRequest）が生成されます。
以下のようにファイルの中身を変更します。

```php:app/Http/Requests/FugaRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class FugaRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'user_name' => 'required',
            'user_name_kana' => [
                'required',
                'regex:/^[ア-ン゛゜ァ-ォャ-ョー]+$/u',
            ]
        ];
    }
}
```
`required`というのは入力必須のバリデーションルールのことです。
`regex`は、正規表現を用いてバリデーションを組めます。今回の例だと、全角カタカナの文字以外は、エラーになるようにバリデーションを組んでいます。
最後のスラッシュに`u`をつけるのを忘れずに。。。という話はこちら↓
[【Laravel】regexを使用するときは、パターン修飾子「u」をつけた方が良いという話](https://qiita.com/hiroaki510/items/8a25d90707f65c55f4b5)

詳しくは[こちらの日本語ドキュメント](https://readouble.com/laravel/5.6/ja/validation.html#available-validation-rules)でご確認を。
**※ちなみにこの正規表現には不備があります。今回書くテストコードでそれが分かります。**
<br>
そして、コントローラー側で以下のようにします。

```php:app/Http/Controllers/HogeHogeController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\FugaRequest;
/* 
   中略 
*/
    public function register(FugaRequest $request)
    {
        /*
            諸々の処理
        */
        return redirect('/register/complete');
    }
```
最後にルーティングの設定をしてあげると、フォーム送信ロジックのだいたいの型ができます。

```php:routes/web.php
Route::post('register', 'HogeHogeController@register');
```
# テストコードの作成
では、本題のテストコード作成を進めていきましょう。
まず、以下のコマンドをターミナルで入力してください。

```bash
$ php artisan make:test FugaRequestTest
```
そうすると、`test/Feature`以下にファイルが作成されるので、以下のように書いてみます。

```php:tests/Feature/FugaRequestTest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\FugaRequest;

class FugaRequestTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @dataProvider dataproviderExample
     */
    public function testExample($item, $data, $expect)
    {
        $request = new FugaRequest();
        $rules = $request->rules();
        $targetRule = array_only($rules, $item);

        $dataList = [$item => $data];
        $validator = Validator::make($dataList, $targetRule);
        $result = $validator->passes();
        $this->assertEquals($expect, $result);
    }

    public function dataproviderExample()
    {
        for ($i=0; $i < 10; $i++) {
            $lead = 'e3';
            $middle = mt_rand(82, 83);

            switch ($middle) {
                case 82:
                    $follow = dechex(mt_rand(161, 191));
                    break;
                case 83:
                    $follow = dechex(mt_rand(128, 182));
                    break;
            }

            $targetWord[] = $lead.$middle.$follow;
        }

        return [
            $targetWord[0] => ['user_name_kana', hex2bin($targetWord[0]), true],
            $targetWord[1] => ['user_name_kana', hex2bin($targetWord[1]), true],
            $targetWord[2] => ['user_name_kana', hex2bin($targetWord[2]), true],
            $targetWord[3] => ['user_name_kana', hex2bin($targetWord[3]), true],
            $targetWord[4] => ['user_name_kana', hex2bin($targetWord[4]), true],
            $targetWord[5] => ['user_name_kana', hex2bin($targetWord[5]), true],
            $targetWord[6] => ['user_name_kana', hex2bin($targetWord[6]), true],
            $targetWord[7] => ['user_name_kana', hex2bin($targetWord[7]), true],
            $targetWord[8] => ['user_name_kana', hex2bin($targetWord[8]), true],
            $targetWord[9] => ['user_name_kana', hex2bin($targetWord[9]), true],
        ];
    }
}
```
はじめに書いたように、ほとんどは[【Laravel】フォームリクエストバリデーションのテストコード作成](https://qiita.com/n_mogi/items/57a946205df2a69889c2)を参考にこの記事を書いてますので、それとは異なる点について述べていきます。
## バリデーションルールの取得方法
`Validator::make`の引数に渡すバリデーションルールのキー（フォーム入力項目）が複数だと、なぜかテスト結果が全て`false`になってしまいました。

```php
// $rules
array:2 [
  "user_name" => "required"
  "user_name_kana" => array:2 [
    0 => "required"
    1 => "regex:/^[ア-ン゛゜ァ-ォャ-ョー]+$/u"
  ]
]
```
したがって、対応処置として以下のようにしました。

```php
$targetRule = array_only($rules, $item);
$dataList = [$item => $data];
$validator = Validator::make($dataList, $targetRule);
```
`array_only`というのは、laravelのヘルパです。
> array_only関数は配列中の指定されたキー／値ペアのアイテムのみを返します。
https://readouble.com/laravel/5.6/ja/helpers.html#method-array-only

こうすると、`Validator::make`の引数に渡すバリデーションルールのキーが一つに定まるので、テストが問題なく動きます。

```php
// $targetRule
array:1 [
  "user_name_kana" => array:2 [
    0 => "required"
    1 => "regex:/^[ア-ン゛゜ァ-ォャ-ョー]+$/u"
  ]
]
```
## 全角カタカナをUTF-8方式でランダムに出力させる
今回のテストで肝になる、フォーム入力の「穴」を見つけるのに手っ取り早いのは、やはりランダムで全角カタカナを出力させる方法でしょう。
そこで私は、文字コードをUTF-8方式でランダムに生成して、それを人間の目でわかるように文字変換する、というやり方にしました。
### UTF-8と全角カタカナ
以下のページを見ると、UTF-8で全角カタカナを表現するには「e382a1」から「e383b6」みたいですね。（伸ばし棒「ー」を除く）
最後の2桁は、16進数で表現されているみたいです。
http://ash.jp/code/unitbl21.htm
http://www.itt-web.net/xeblog/index/action_xeblog_details.1/blog_id.549.html
### ランダム生成
では、ランダム生成のやり方を考えていきます。
私は、上記であげた2番目のページに書かれているマトリクスがわかりやすいと思ったので、それを参考にしました。
まず、6桁のうち先頭の2文字は「e3」で全て共通ですので、`$lead = 'e3';`としました。
次の2桁は82か83です。なので私はPHPのmt_rand関数を使用して、どちらか一方を`$middle`に代入することにしました。
> mt_rand — メルセンヌ・ツイスター乱数生成器を介して乱数値を生成する
mt_rand ( int $min , int $max ) : int
https://www.php.net/manual/ja/function.mt-rand.php

最後の2桁は少々ややこしく、`$middle`が82であればa1からbfですが、83であれば80からb6です。
なのでここは`switch文`を使用して出し分けることにします。
また、範囲指定で16進数の値を乱数生成する関数はPHPに無いらしく、まず10進数の数字をランダムに生成してそれを16進数に変換するというやり方で対応します。
以上を踏まえて、次のようなコードを書きました。

```php
switch ($middle) {
    case 82:
        // 10進数から16進数への変換 161 → a1, 191 → bf
        $follow = dechex(mt_rand(161, 191));
        break;
    case 83:
        // 10進数から16進数への変換 128 → 80, 182 → b6
        $follow = dechex(mt_rand(128, 182));
        break;
}
```
最後に、それぞれの変数を文字列連結して配列に代入する、というのを`for文`で回しました。（[上記参照](https://qiita.com/hiroaki510/items/f49fcde808f10b55959f#%E3%83%86%E3%82%B9%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%BD%9C%E6%88%90)）
### 文字への変換
ここまででは、配列に入ってる値は人間から見ればただの英数字の羅列です。
人間でも読めるように全角カタカナに変換しましょう。
ということで、`return`の配列の中で`hex2bin`関数を使用します。
> hex2bin — 16進エンコードされたバイナリ文字列をデコードする
hex2bin ( string $data ) : string
https://www.php.net/manual/ja/function.hex2bin.php

```php:tests/Feature/FugaRequestTest.php
return [
    $targetWord[0] => ['user_name_kana', hex2bin($targetWord[0]), true],
    /* 以下略 */
```

なぜ`return`の配列の中で使用するのかというと、テストに失敗した文字コードが出力された方が分かりやすいと感じたからです。
（今回のテストでは範囲外の文字ですが）例えば、失敗すると以下のようになります。

```
1) Tests\Feature\FugaRequestTest::testExample with data set "e383b8" ('user-name-kana', 'ヸ', true)
Failed asserting that false matches expected true.
```
これであれば失敗した「e383b8」は「ヸ」なんだな、と直感的に分かります。
まあここら辺はもっと良い方法があるような気がしますが。。。
# テスト実行
それでは、テストを実行してみましょう。
以下のような結果となりました。

```bash
$ vendor/bin/phpunit tests/Feature/FugaRequestTest.php
PHPUnit 7.2.7 by Sebastian Bergmann and contributors.

.........F                                                        10 / 10 (100%)

Time: 181 ms, Memory: 12.00MB

There was 1 failure:

1) Tests\Feature\FugaRequestTest::testExample with data set "e383b4" ('user-name-kana', 'ヴ', true)
Failed asserting that false matches expected true.

/Users/hiro/Developer/stock_app/tests/Feature/FugaRequestTest.php:28

FAILURES!
Tests: 10, Assertions: 10, Failures: 1.
```
うーん、「ヴ」ってフリガナに入力する人がいるのか、、、
と思いましたが、0ではないと思うので（外国の方とか）一応バリデーションに引っかからないようにした方が良いかもしれませんね。
ということで、`FugaRequest.php`に書いた`regex`をちょっと修正しました。

```
regex:/^[ァ-ヶー]+$/u
```
最初に書いたやつと比べてだいぶスッキリしましたね！
テストコード書いてみて良かった☆（雑な締め）