@faker-js/faker の内部実装を探る:ダミーデータ生成の仕組み
この記事はファンタラクティブ 2024 年アドベントカレンダー 12 月 9 日の記事です
はじめに
@faker-js/faker(以下 faker とします) は、ダミーデータを生成するためのライブラリです。
テストで使用したり、モックデータを生成するのに便利なライブラリです。
この記事では faker が内部でどのような処理が行われているのかを簡単に見ていき、faker の実装についての理解を深めることを目的とします。
TL;DR
const firstName = faker.person.firstName();
上記コードは、person モジュールの firstName メソッドを実行した時の処理です。
このコードを実行した時の処理を図示したものが以下になります。
faker インスタンス
faker は主に以下のようなコンセプトを持っています。
もしかしたら他にも挙げることができるかもしれませんが、この記事ではこれらのコンセプトを中心に見ていきます。
- Modules (for basic datatypes and topic specific modules)
- Definitions
- Helpers
- Randomizer
Modules (for basic datatypes and topic specific modules)
basic datatypes(日付や文字列、数値などを扱う)モジュールと、topic specific(Animal、Food、Person などを扱う)なモジュールがあります。
// basic datatypes
const randomNumber = faker.number.int(); // 2900970162509863
// topic specific
const randomBear = faker.animal.bear(); // "Asian black bear"
Definitions
Definitions は、実際にダミーデータを生成するために使用されるデータを指します。
Definitions は、faker インスタンスに渡す locale の値によって、扱うデータが変わります。
例えば、日本語のデータを扱いたい場合、以下のように fakerJA を import してアプリケーションで使用します。
import { fakerJA } from "@faker-js/faker";
これは、faker 内部では以下を実行しています。
import ja from "../locales/ja";
export const faker = new faker({
locale: [ja, en, base], // localeに`ja`を指定
});
ja
は、ja locale の definitions のリスト を指します。以下のようなデータを持っています。
const ja: LocaleDefinition = {
company: {
category: ["ガス", "保険", "印刷", ...],
// 省略
},
person: {
first_name: {
generic: ["あゆみ", "きみ", ...],
female: ["千代子", ...],
male: ["正一", ...],
},
// 省略
},
// 省略
};
faker はこれらの definitions を元に、ダミーデータを生成します。
Helpers
ダミーデータを生成するのに便利なヘルパーを指します。
厳密にはヘルパーモジュールというモジュールの一つですが、先に挙げたModules
とは少し性質が異なるので、筆者が意図的に分けています。
特に便利なメソッドは arrayElement()
で、配列からランダムな要素を返します。
const randomAnimal = faker.helpers.arrayElement(["cat", "dog", "mouse"]); // "dog"
他にも、weightedArrayElement()
を使用して、配列の要素に重みをつけ、重みに応じた確率でランダムな要素を返すことができたり、fromRegExp()
を使用して、正規表現にマッチするランダムな文字列を返す(一部制限あり)ことができたりと、便利なメソッドがあります。
詳しくは Helpers のドキュメントをご覧ください。
Randomizer
Randomizer は乱数を生成するための処理を指し、faker ではこれをカスタマイズすることができます。
デフォルトでは、メルセンヌ・ツイスタという擬似乱数生成器を使用します。元々 C 言語で実装された「MT19937 アルゴリズム」を TypeScript で実装して乱数を生成しているようです。
詳しくは実装を確認してみてください。
この Randomizer は、例えば Number モジュールのint()
メソッドで乱数を生成するために使用されています。
faker.number.int(); // 2900970162509863
faker.number.int(100); // 52
faker.number.int({ min: 1000000 }); // 2900970162509863
faker.number.int({ max: 100 }); // 42
faker.number.int({ min: 10, max: 100 }); // 57
faker.number.int({ min: 10, max: 100, multipleOf: 10 }); // 50
このint()
メソッドはまた、先ほどの Helpers のセクションで挙げたarrayElement()
メソッドで、引数である配列のarray.length - 1
を上限とした乱数を生成するために使用されています。
/**
* Returns random element from the given array.
*
* @template T The type of the elements to pick from.
*
* @param array The array to pick the value from.
*
* @throws If the given array is empty.
*
* @example
* faker.helpers.arrayElement(["cat", "dog", "mouse"]) // "dog"
*
* @since 6.3.0
*/
arrayElement<const T>(array: ReadonlyArray<T>): T {
if (array.length === 0) {
throw new fakerError("Cannot get value from empty dataset.");
}
const index =
array.length > 1 ? this.faker.number.int({ max: array.length - 1 }) : 0;
return array[index];
}
Person モジュール
名前や役職などの個人情報を生成するモジュールです。
詳しくはこちらをご覧ください。
firstName メソッドの実装を見る
firstName
メソッドは、Person モジュールにおける、Person の firstName をランダムに取得するためのメソッドです。
これまで見てきたことを踏まえて、実装を確認してみます。
以下のコードは、firstName メソッドの実装です。
firstName(sex?: SexType): string {
return this.faker.helpers.arrayElement(
selectDefinition(
this.faker,
sex,
this.faker.definitions.person.first_name
)
);
}
初登場のselectDefinition()
の実装を確認しましょう。
/**
* Select a definition based on given sex.
*
* @param faker faker instance.
* @param sex Sex.
* @param personEntry Definitions.
*
* @returns Definition based on given sex.
*/
function selectDefinition<T>(
faker: faker,
sex: SexType | undefined,
personEntry: PersonEntryDefinition<T>
): T[] {
const { generic, female, male } = personEntry;
switch (sex) {
case Sex.Female: {
return female ?? generic;
}
case Sex.Male: {
return male ?? generic;
}
default: {
return (
generic ??
faker.helpers.arrayElement([female, male]) ??
// The last statement should never happen at run time. At this point in time,
// the entry will satisfy at least (generic || (female && male)).
// TS is not able to infer the type correctly.
[]
);
}
}
}
JSDoc のコメントからわかる通り、selectDefinition()
は、与えられたsex
の値に応じて、definitions の配列を返します。
先ほど、definitions のセクションで見たように、definitions は 以下のようになっていました。
sex
がundefined
の場合は、generic の definition 配列を返し、それ以外の場合は、female か male の definition 配列を返すということですね。
const ja: LocaleDefinition = {
company: {
category: ["ガス", "保険", "印刷", ...],
// 省略
},
person: {
first_name: {
generic: ["あゆみ", "きみ", ...],
female: ["千代子", ...],
male: ["正一", ...],
},
// 省略
},
// 省略
};
そして、arrayElement()
は、先ほど Helpers のセクション で見たように、配列からランダムな要素を返すメソッドです。
ということでまとめると firstName()
メソッドは、
- definitions からデータを取得
- 取得した definition 配列からランダムな要素を返す
上記の流れでランダムな firstName を出力していることがわかりました。
おわりに
この記事では、faker の内部実装を簡単に見てきました。
実行すると色々な結果が返ってくる topic specific なデータは一見魔法のようにも感じますが、実際には、事前に定義されているデータから、乱数を生成して取得していることがわかりました。面白いですね。
また、Person モジュールのbio()
メソッドなどは出力の pattern を定義し、他メソッドもしくは他モジュールの definitions と組み合わせて最終的な出力をするなど、いろいろなメソッドがあり面白いので気になった方はぜひ覗いてみてください。
明日は moyai さんの記事になります!
参考
ユーザーファーストなサービスを伴に考えながらつくる、デザインとエンジニアリングの会社です。エンジニア積極採用中です!hrmos.co/pages/funteractive/jobs
Discussion