【Laravel5.8】Faker::realText()を叩き続けると死ぬ

LaravelでAPIのテストに[fzaninotto/Faker](https://github.com/fzaninotto/Faker)を使っていたわけですが、こいつは下手に使うと死にます。

Fakerって何、って人は[Fakerでランダムなフェイクデータを作成する](https://qiita.com/Sa2Knight/items/fb82be7551cc84764267)とか[Faker@PHPでダミーデータを作る](https://qiita.com/zaburo/items/4487b23543ce88ce7f0c)とか見るといいと思います。
Laravelからは[WithFakerトレイト](https://laravel.com/api/5.8/Illuminate/Foundation/Testing/WithFaker.html)で[さくっと使え](https://qiita.com/kazuhei/items/f0766a55a219d6e329f3)ます。


# メモリエラーで死ぬ

```php
class ApiTest extends TestCase {
    use WithFaker;
	
	/**
	* @test
	* なんかAPI呼ぶテスト
	*/
    public function testAPI(){
    	$params = [
    		'value1' = $this->faker('ja_JP')->realText($this->faker('ja_JP')->numberBetween(100, 1000)),
    		'value2' = $this->faker('ja_JP')->realText($this->faker('ja_JP')->numberBetween(100, 1000)),
    		'value3' = $this->faker('ja_JP')->realText($this->faker('ja_JP')->numberBetween(100, 1000)),
    		'value4' = $this->faker('ja_JP')->realText($this->faker('ja_JP')->numberBetween(100, 1000)),
    		'value5' = $this->faker('ja_JP')->realText($this->faker('ja_JP')->numberBetween(100, 1000)),
    	];
    	
        $response = $this->postJson('api/test', $params);
        $response
            ->assertStatus(200)
            ->assertJsonStructure([
                'response' => [
                    'values' => [
                        '*' => [
                            'id',
                            'value',
                        ],
                    ],
                ],
            ]);
    }
}
```

こんなかんじのテストを100個くらい書いたら`Allowed memory size of 134217728 bytes exhausted`とか言われました。
ただ単にAPIを呼んでるだけだというのに、いったい何が128メガバイトも使ってるんだ。


# 原因

調査過程をすっ飛ばして原因を答えると、[Faker\Provider\Text::getConsecutiveWords](https://github.com/fzaninotto/Faker/blob/v1.8.0/src/Faker/Provider/Text.php#L77)です。
ここには、`realText`で返す内容を作るために[元ネタのテキスト](https://github.com/fzaninotto/Faker/blob/v1.8.0/src/Faker/Provider/ja_JP/Text.php#L25)からランダムな文字列を生成する処理が書かれています。
で、このメソッド、一回呼ばれるごとに6メガバイト消費します。

本来は一度呼ばれたら後はキャッシュを使うので、Fakerインスタンスを使い回す場合は最初の一回だけgetConsecutiveWordsが呼ばれます。

```php
    $faker = Faker::create('ja_JP');
	$params = [
		'value1' = $faker->realText($faker->numberBetween(10, 20)), // getConsecutiveWordsが呼ばれる
		'value2' = $faker->realText($faker->numberBetween(10, 20)), // 以降呼ばれない
		'value3' = $faker->realText($faker->numberBetween(10, 20)),
		'value4' = $faker->realText($faker->numberBetween(10, 20)),
		'value5' = $faker->realText($faker->numberBetween(10, 20)),
	];
```

しかし`$this->faker('ja_JP')`は毎回インスタンスを生成するため、毎回`getConsecutiveWords`が呼ばれ、結果として冒頭の`ApiTest::testAPI`は、1メソッドで30メガバイト消費します。

しかもメソッドが終了してもメモリは解放されません。
なんでだ。


# 修正

WithFakerトレイトをやめて、地道にテスト毎にインスタンス生成しよう。

```php
class ApiTest extends TestCase {

    protected function setUp(): void{
        parent::setUp();
        $this->faker = Faker::create('ja_JP');
    }

    public function testAPI(){
    	$params = [
			'value1' = $this->faker->realText($this->faker->numberBetween(10, 20)),
			'value2' = $this->faker->realText($this->faker->numberBetween(10, 20)),
			'value3' = $this->faker->realText($this->faker->numberBetween(10, 20)),
			'value4' = $this->faker->realText($this->faker->numberBetween(10, 20)),
			'value5' = $this->faker->realText($this->faker->numberBetween(10, 20)),
    	];
    	/* 以下略 */
    }
}
```

`TestCase::setUp`に書いてもいいかもね。


# だめだった

予想通り駄目でした。

ファイルを超えてのFakerインスタンスの共有はできないため、テストファイルひとつごとに6メガバイト増えます。
`realText`一回ごとに6メガから、ファイルひとつごとに6メガとだいぶマシにはなったものの、テストファイルが何十個もあるのでしにました。


# シングルトン

シングルトン返すtraitでも作ろうかと思ったけどやめた。

いやだってさ、ほら、なんかさ、シングルトンって言うと怖い人たちがやってくるじゃん。


# 諦めた

```php
	'value1' = implode(' ', $faker->words(random_int(100, 1000)))
```

全然メモリ消費しないよ。
やったね。

英語だけど。


# 助けてアルゴマン

助けてアルゴマン