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)))
全然メモリ消費しないよ。
やったね。
英語だけど。
助けてアルゴマン
助けてアルゴマン