faker を使ってダミーデータを生成する

これこれ、このライブラリ。

fzaninotto/Faker: Faker is a PHP library that generates fake data for you

ざっくり翻訳しつつ、日本語データでの使い方へ。

faker とは

faker は PHP のライブラリで、偽のデータを提供する。データベースの初期化や XML ドキュメントの生成、ストレステスト、本番データの匿名化、などに活用できる。faker は Perl の Data::Faker と Ruby の Faker に影響を受けている。

faker の使い方

インストール方法

Pakagist にて提供されているので composer を使ってインストールできる。

composer require fzaninotto/faker

基本的な使い方

$faker = \Faker\Factory::create();
// faker オブジェクトの生成

echo $faker->name;
// 名前。例えば 'Lucy Cechtelar'

echo $faker->address;
// 住所。例えば
// "426 Jordy Lodge
// Cartwrightshire, SC 88120-6700"

echo $faker->text;
// 文章…というよりは Lorem 。例えば。
// Dolores sit sint laboriosam dolorem culpa et autem. Beatae nam sunt fugit
// et sit et mollitia sed.
// Fuga deserunt tempora facere magni omnis. Omnis quia temporibus laudantium
// sit minima sint.

faker には嬉しいことに、日本語を含む色々な言語のダミーデータをサポートしている。ここでは日本語での扱いを試してみる。

// 引数に言語を指定すると利用してくれる
$faker = \Faker\Factory::create('ja_JP');

echo $faker->name;
// 例えば '大垣 直子'

echo $faker->address;
// 例えば '2636573  滋賀県浜田市西区笹田町渡辺1-1-10'

echo $faker->realText;
// text は Lorem なので realText がある
// 例えば 'オン燈と、もうすっかりがきの音にすきっと思いなやさで伝つたわ」「なんだ」カムパネルラはこち見ていたの...'

どういうプロパティが用意されているか

英語プロパティは README に全てあって、n - m の範囲で乱数とか、「Dr.」などの敬称を使えるとか、日時とか、まあなんか色々とあるのでそっちを見ると良さそう。

Faker/readme.md at master · fzaninotto/Faker

日本語のだけ、ソースとにらめっこしないと分からないところがあったので、取り上げる。(網羅できてないかも)

country 国名
prefecture 都道府県
city
ward
streetAddress 町以下
postcode 郵便番号
secondaryAddress マンション名
company 会社名
userName ユーザ名
domainName ドメイン名
email メールアドレス
name 名前(姓 + 名)
lastName
firstName
firstNameMale 名(男性)
firstNameFeMale 名(女性)
kanaName 名前カナ
lastKanaName 姓カナ
firstKanaName 名カナ
firstKanaNameMale 名カナ(男性)
firstKanaNameFemale 名カナ(女性)
phoneNumber 電話番号
realText 日本語文章

ORMとの連携

faker は CakePHP や Laravel の ORM と連携することができる(!?)
試してみたところ、連携というよりは ORM を通して、実際に DB に値を格納してくれるっぽい。

例えば CakePHP3 で使うならこんなふうに。

$faker = \Faker\Factory::create('ja_JP');
$populator = new \Faker\ORM\CakePHP\Populator($faker);
$populator->addEntity('Users', 5, [
    'name'       => function() use ($faker) { return $faker->name; },
    'prefecture' => function() use ($faker) { return $faker->prefecture; },
    'created'    => null,
    'modified'   => null,
]);
$inserted = $populator->execute();

$Users = \Cake\ORM\TableRegistry::get('Users');
debug($Users->find('all')->toList());
// 例としてこのような出力になる
// [
//     (int) 0 => object(App\Model\Entity\User) {
//         'id' => (int) 1,
//         'name' => '津田 裕樹',
//         'prefecture' => '栃木県',
//         ....
//         'created' => object(Cake\I18n\FrozenTime) {
//             'time' => '2017-11-20T13:47:32+09:00',
//             'timezone' => 'Asia/Tokyo',
//             'fixedNowTime' => false
//         },
//         'modified' => object(Cake\I18n\FrozenTime) {
//             'time' => '2017-11-20T13:47:32+09:00',
//             'timezone' => 'Asia/Tokyo',
//             'fixedNowTime' => false
//         },
//     },
//     ...
// ] 

特に指定をしなかったカラムについては DB の形を見て、結構いい感じに適当に faker な値で埋めてくれる。指定はクロージャで、 faker を使ってランダムなデータを作ることも出来るし、固定値にすることもできる。また、例としては 1 エンティティしか作らなかったが、addEntity を複数呼んで、 まとめて execute することで複数のエンティティも作ることができる。

シード値を設定する

テストで使うときやっぱりランダムだとつらいよね、というときのためにシード値を設定することができる。

$faker = \Faker\Factory::create();
$faker->seed(1234);

echo $faker->name;
// 何度コードを流しても以下の順番で流れる
// 'Miss Lorna Dibbert'
// 'Litzy Emard'
// 'Odessa Collins'
// ...

ランダムに提供される部分はそのとおりなのだが、日付については引数を指定しないと now() な値が使われるのでシードは同じでも出力される値が変わってしまうので、なんでもいいが毎回同じ値を入れる必要がある。

$faker = \Faker\Factory::create();
$faker->seed(1234);
echo $faker->dateTime();
// ランダムになる

$faker = \Faker\Factory::create();
$faker->seed(1234);
echo $faker->dateTime('2017/01/01 12:00:00');
// 固定パターンになる

faker には乱数を提供する関数もあり、シード値を設定することで、こういったものたちも固定パターン化される。便利ちゃん。

$faker = \Faker\Factory::create();
$faker->seed(1234);
echo $faker->numberBetween(0,100);
// 固定パターンになる
// 81, 50, 62, 18, 56, ...

faker の内部、プロバイダの話

何の気無しに \Faker\Factory::create() を呼び出していたが、実は内部でこんなような処理をしている。

$faker = new \Faker\Generator();
$faker->addProvider(new \Faker\Provider\en_US\Person($faker));
$faker->addProvider(new \Faker\Provider\en_US\Address($faker));
$faker->addProvider(new \Faker\Provider\en_US\PhoneNumber($faker));
$faker->addProvider(new \Faker\Provider\en_US\Company($faker));
$faker->addProvider(new \Faker\Provider\Lorem($faker));
$faker->addProvider(new \Faker\Provider\Internet($faker));

※実際のコードはこのあたり。
Faker/Factory.php at master · fzaninotto/Faker

プロバイダの土台として \Faker\Provider\Base がいるので、これを継承した適当なクラスを作り、 public なメソッドを作って return すれば良い。そのプロバイダクラスを new して addProvider すれば使えるようになる。

class Book extends \Faker\Provider\Base
{
  public function title($nbWords = 5)
  {
    $sentence = $this->generator->sentence($nbWords);
    return substr($sentence, 0, strlen($sentence) - 1);
  }

  public function ISBN()
  {
    return $this->generator->ean13();
  }
}


$faker->addProvider(new Book($faker));

echo $faker->ISBN;

faker を使ってみて

説明にもあるが、テストコードにおいて、いや、そこはなんでも良いんだけど、それっぽい住所や名前をよろしく入力して欲しい、みたいな時。あるいは、DBの初期化時に、多いに力を発揮しそうな感触。

テストコードとして利用する場合、値が毎回ランダムになって使い所がイマイチわかりにくい気もちょっとするが、それで詰まるときってそんなに無いのでは…。値が被って~~~うあああ!!!!みたいなことはあると思う。

話はそれるが最近見かけた property-based testing という方法にはめちゃんこ合っていると思う。

Property-Based Testing for Godly Tests

そもそも PHPUnit なら Data Provider の仕組みがあるし、あるいは CakePHP なら Fixture が、 Laravel には Factory の仕組みがそれぞれにある(きっと他のフレームワークにもあるんじゃないかな、調べてない。。)ので、そういうこともやりやすいと思う。
ちなみに Laravel には faker が既に含まれていて、 Factory の仕組みを使うとガンガンにテストデータを作ってくれる。スゴイ。

Database Testing - Laravel - The PHP Framework For Web Artisans

ORMとの連携はできるとはいえ、既に書いたとおり、各フレームワークがそういう仕組みを提供しているので、それとバッティングするなあという気持ちも。その中で使っていく、みたいなイメージなのかな。。ちょっとわからないや。。

前後の記事

Next:
Prev: