LoginSignup
17
14

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-04-08

LaravelでAPIのテストにfzaninotto/Fakerを使っていたわけですが、こいつは下手に使うと死にます。

Fakerって何、って人はFakerでランダムなフェイクデータを作成するとかFaker@PHPでダミーデータを作るとか見るといいと思います。
LaravelからはWithFakerトレイトさくっと使えます。

メモリエラーで死ぬ

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です。
ここには、realTextで返す内容を作るために元ネタのテキストからランダムな文字列を生成する処理が書かれています。
で、このメソッド、一回呼ばれるごとに6メガバイト消費します。

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

    $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トレイトをやめて、地道にテスト毎にインスタンス生成しよう。

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でも作ろうかと思ったけどやめた。

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

諦めた

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

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

英語だけど。

助けてアルゴマン

助けてアルゴマン

17
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
14