Laravel:<めざせテスト巧者編> fakerのuniqueメソッド

Date:

Share post:

テストを行う際にfakerの使用は必須とも言えます。
様々なフェイクデータを作成してくれますが、あくまでランダムな生成であるため複数のデータを生成した際に重複したデータが含まれることもあります。
一方で、テストケースによっては重複なしに複数のデータを生成したい場合もあります。
このような時に使用するのがuniqueメソッドです。

uniqueメソッドの使い方

使い方は簡単で、データ生成メソッドに先行してuniqueメソッドを呼び出すのみです。
例えば0〜9までの数字を10個、ランダムかつ重複なく取得する実験として以下のような処理を実行してみます。

for ($idx=0; $idx < 10; $idx++) {
    echo $faker->unique()->numberBetween(0,9)."\n";
}

この結果は以下のようになります。

0                                                                                                                                                                                                                                               
3                                                                                                                                                                                                                                               
9                                                                                                                                                                                                                                               
8                                                                                                                                                                                                                                               
2                                                                                                                                                                                                                                               
1                                                                                                                                                                                                                                               
5                                                                                                                                                                                                                                               
6                                                                                                                                                                                                                                               
7                                                                                                                                                                                                                                               
4                                                                                                                                                                                                                                               
7

確かに重複なく10個の数字が取得できています。

ところで上記処理では候補が10個しかありませんが11個目を取得しようとするとどうなるでしょう?

for ($idx=0; $idx < 10; $idx++) {
    echo $faker->unique()->numberBetween(0,9)."\n";
}
echo $faker->unique()->numberBetween(0,9)."\n";

上記結果は以下のエラーとなります。

OverflowException: Maximum retries of 10000 reached without finding a unique value

候補が10個しかないのに10000回リトライしたっぽいことを言っていますが、この辺は単なる定型文かもしれません。

もう少しアレンジして以下のようにしてみます。

for ($idx=0; $idx < 10; $idx++) {
    echo $faker->unique()->numberBetween(0,9)."\n";
}   
echo $faker->numberBetween(0,9)."\n";
echo $faker->unique()->numberBetween(0,9)."\n";

上記の場合、4行目のunique指定していない処理は正常に実施できますが、5行目でエラーになります。

候補を増やした場合はどうでしょう?

for ($idx=0; $idx < 10; $idx++) {
    echo $faker->unique()->numberBetween(0,9)."\n";
}   
echo $faker->unique()->numberBetween(0,10)."\n";

この場合は4行目で10が出力されます。
各処理において、指定した範囲に未使用のものがあれば使えるようです。

同じように数字を取得する操作を行なっても、データ生成のメソッドが異なれば問題ないです。

for ($idx=0; $idx < 3; $idx++) {
    print_r([
        $faker->unique()->numberBetween(0,2),
        $faker->unique()->randomElement([0,1,2])
    ]);
}

上記結果は以下の通り。

Array                                                                                                                                                                                                                                           
(                                                                                                                                                                                                                                               
    [0] => 1                                                                                                                                                                                                                                    
    [1] => 2                                                                                                                                                                                                                                    
)                                                                                                                                                                                                                                               
Array                                                                                                                                                                                                                                           
(                                                                                                                                                                                                                                               
    [0] => 0                                                                                                                                                                                                                                    
    [1] => 1                                                                                                                                                                                                                                    
)                                                                                                                                                                                                                                               
Array                                                                                                                                                                                                                                           
(                                                                                                                                                                                                                                               
    [0] => 2                                                                                                                                                                                                                                    
    [1] => 0                                                                                                                                                                                                                                    
)

この辺は予定通りですが。

他のデータ形式で試してみる

前述したnumberBetweenやrandomElementでは母集団を明示的に指定していますが、母集団の見えていないデータ形式ではどうなるでしょう?

for ($idx=0; $idx < 200; $idx++) {
    echo $faker->unique()->word."\n";
}

上記は先に示したエラーになりました。
色々試してみると、私の環境では182個が限界で、183個以上を指定するとエラーになります。
意外に候補が少ないようです。

sentenceではどうでしょう?

for ($idx=0; $idx < 10000; $idx++) {
    echo $faker->unique()->sentence."\n";
}

これは普通にOKでした。

ちなみに上記で試行回数を10,000にしたのは先のエラーメッセージで10,000辺りに何らかの閾値がありそうであったためですが、実はsentenceに関しては10倍の100,000でも問題ありませんでした。
emailやurlも同様でした。

sentence,email,urlなどではfakerが保持している単語の候補を組み合わせて文字列を生成しているような印象です。
単語の候補は見た感じwordの候補と同じ(つまり182個)で、仮にそれを2つ組み合わせただけでも30,000以上のパターンを生成できるので、重複しない文字列を生成しやすいのだと推測します。

lastName, firstNameも試してみましょう。

lastNameは51個が限界でした(意外に少ない)。
せっかくなので、それら候補を紹介しておきます。

三宅, 中島, 中村, 中津川, 井上, 井高, 伊藤, 佐々木, 佐藤, 加納, 加藤, 原田, 吉本, 吉田, 喜嶋, 坂本, 大垣, 宇野, 宮沢, 小林, 小泉, 山口, 山岸, 山本, 山田, 工藤, 廣川, 斉藤, 木村, 杉山, 村山, 松本, 桐山, 江古田, 津田, 浜田, 渚, 渡辺, 田中, 田辺, 石田, 笹田, 若松, 藤本, 西之園, 近藤, 野村, 鈴木, 青山, 青田, 高橋

なんかメジャーな名字(山崎とか)が含まれていない一方で、あまり聞いたことのない名字が含まれていて、選定基準が今一つ謎です。

firstNameに関しても同じく51個が上限でした。この51という数字にどんな意味があるんでしょう?
こちらも候補の全てを紹介しておきます。

あすか, くみ子, さゆり, 七夏, 京助, 亮介, 修平, 健一, 充, 加奈, 千代, 和也, 太一, 太郎, 学, 幹, 康弘, 拓真, 明美, 春香, 晃, 智也, 桃子, 治, 洋介, 浩, 涼平, 淳, 直人, 直子, 直樹, 真綾, 知実, 稔, 篤司, 結衣, 美加子, 翔太, 翼, 聡太郎, 舞, 花子, 英樹, 裕太, 裕樹, 裕美子, 里佳, 陽一, 陽子, 零, 香織

こちらも謎のマニアックさを感じます。どこか特定の名簿から抽出してきたんでしょうかね?

総括

uniqueは重複なくフェイクデータを取得できる便利機能ですが、対象データによっては候補が少ないものもあるようです。
特にfactory内で使用している場合、生成するレコード数によっては簡単に上限に到達してしまうことも考えられます。

上限の制約を受けずに任意の数字や文字列を生成したい場合はregexifyメソッドを使用する方法もあるようですが、その辺は次回の投稿で。

Related articles

ローカルSMTPメールサーバ(Mailpit)をE...

ローカル環境でのメール送受信テストにつ...

EC-CUBE 4系のプラグイン開発について その...

今回は、ちょっとハマったプラグインのイ...

EC-CUBE 4系のプラグイン開発について その...

前回のブログの最後でちょっと書いたので...

EC-CUBE 4系のプラグイン開発について その...

前回、プラグインを一旦有効化させて管理...