Pythonでテストデータを生成するライブラリfakerのコードリーディング


##はじめに
このあいだ@TakesxiSximadaさんが[Pythonでパスワードの強度をしらべるライブラリSafeのコードリーディング](http://qiita.com/TakesxiSximada/items/de86eb4bbfe11810fd0e)という記事をあげていて面白そうだったので、自分もやってみました。

そこで以前からちょっと使ってみようと気になっていたfakerというライブラリを取り上げてみました。

fakerはダミーなテストデータをいい感じに生成してくれるライブラリです。他の言語でもよく見かけるやつのpython版です。

https://pypi.python.org/pypi/fake-factory/0.5.3
https://github.com/joke2k/faker

##Install
pipでインストールできます。

```
$ pip install fake-factory
```

##READMEのsampleコードを試す
※ 実行環境のpythonバージョンは3.5.0です。
とりあえずimport。

```
>>> from faker import Factory
```

そしてテストデータを作ってくれるgeneratorを生成すればOK。

```
>>> fake = Factory.create()
```

あとはこんな感じでテストデータが返ってきます。

```
>>> fake.name()
'Anfernee Reichel'
>>> fake.address()
'084 Tiney Fork Suite 757\nPort Earl, MI 20240-1776'
>>> fake.text()
'Facilis non eligendi qui deleniti ullam est. Ab minus est non et occaecati laborum sequi. Vero consectetur repellendus dicta velit. Quisquam omnis alias error sed totam.'
```
多言語化にも対応していて、さっきの`Factory.create()`に`locale`を引数に渡してあげることで実現できます。

```
>>> fake = Factory.create('ja_JP')
>>> fake.name()
'津田 裕美子'
>>> fake.address()
'群馬県中央区芝公園32丁目22番3号 上広谷ハイツ400'
>>> fake.text()
'Non ut in unde ipsa fugiat excepturi voluptate. Enim molestias voluptatem aperiam. Est fuga distinctio sit officia qui velit numquam sint.'
```

textには日本語データが用意されてなかったので、デフォルトの`en_US`のデータが返ってきてます。

ちなみに'fake`は`faker.generator.Generator()`のインスタンスです。

```
>>> type(fake)
<class 'faker.generator.Generator'>
```

##コードリーディング
それではコードを読んでいきたいのですが、
その前にfakerではProviderが何者なのかを理解できた方が話が早いので、まずProviderに関して説明していきます。

###Provider - テストデータを提供してくれる

各Providerは [faker/faker/providers](https://github.com/joke2k/faker/tree/master/faker/providers) 配下に格納されてます。

```
├── providers
│   ├── __init__.py
│   ├── __pycache__
│   ├── address
│   ├── barcode
│   ├── color
│   ├── company
│   ├── credit_card
│   ├── currency
│   ├── date_time
│   ├── file
│   ├── internet
│   ├── job
│   ├── lorem
│   ├── misc
│   ├── person
│   ├── phone_number
│   ├── profile
│   ├── python
│   ├── ssn
│   └── user_agent
```

`address `や`barcode`などのカテゴリ毎に各言語に対応したディレクトリと、各カテゴリのBaseとなるProviderを実装しています。

ここでは`person`に注目して、ソースを追っていきます。
`person`配下はこんな感じになってます。

```
├── providers
│   ├── __init__.py
│   ├── person
│   │   ├── __init__.py
│   │   ├── bg_BG
│   │   ├── cs_CZ
│   │   ├── de_AT
│   │   ├── de_DE
│   │   ├── dk_DK
│   │   ├── el_GR
│   │   ├── en
│   │   ├── en_US
│   │   ├── es_ES
│   │   ├── es_MX
│   │   ├── fa_IR
│   │   ├── fi_FI
│   │   ├── fr_FR
│   │   ├── hi_IN
│   │   ├── hr_HR
│   │   ├── it_IT
│   │   ├── ja_JP
│   │   ├── ko_KR
│   │   ├── lt_LT
│   │   ├── lv_LV
│   │   ├── ne_NP
│   │   ├── nl_NL
│   │   ├── no_NO
│   │   ├── pl_PL
│   │   ├── pt_BR
│   │   ├── pt_PT
│   │   ├── ru_RU
│   │   ├── sl_SI
│   │   ├── sv_SE
│   │   ├── tr_TR
│   │   ├── uk_UA
│   │   ├── zh_CN
│   │   └── zh_TW
```


次に、`/faker/providers/person`直下の[`__init__.py`](https://github.com/joke2k/faker/blob/master/faker/providers/person/__init__.py)を見てみます。

```pytyon
from .. import BaseProvider


class Provider(BaseProvider):
    formats = ['{{first_name}} {{last_name}}', ]

    first_names = ['John', 'Jane']

    last_names = ['Doe', ]

    def name(self):
        """
        :example 'John Doe'
        """
        pattern = self.random_element(self.formats)
        return self.generator.parse(pattern)

    @classmethod
    def first_name(cls):
        return cls.random_element(cls.first_names)

    @classmethod
    def last_name(cls):
        return cls.random_element(cls.last_names)
        
    # 以下省略
```

こんな感じで各言語のPersonProviderのベースとなるProviderが実装されてます。
`random_element()`などランダムにデータを抽出するclassmethod群を実装した[BaseProvider](https://github.com/joke2k/faker/blob/master/faker/providers/__init__.py)を継承しているのがわかります。


そして、このProviderを継承してpropertyとmethodを新たに作ったり、オーバーライドしたりして各言語に対応したProviderを用意していきます。
日本語対応のPersonProviderは以下を参照ください。
https://github.com/joke2k/faker/blob/master/faker/providers/person/ja_JP/__init__.py

###Factory.create() - Generatorを生成する
https://github.com/joke2k/faker/blob/master/faker/factory.py#L14-L44

このメソッドでは`<class 'faker.generator.Generator'>`のインスタンスを生成してreturnしてます。

下記の処理で、`Factory.create()`の引数として渡した`locale`を元に、`<class 'faker.generator.Generator'>`のインスタンスである`faker`に各Providerをセットしています。
（指定した`locale`に対応したProviderがない場合は、DEFAULT_LOCALEである`en_US`のものがセットされます）

```
for prov_name in as:
    if prov_name == 'faker.as':
        continue

    prov_cls, lang_found = cls._get_provider_class(prov_name, locale)
    provider = prov_cls(faker)
    provider.__provider__ = prov_name
    provider.__lang__ = lang_found
    faker.add_provider(provider)
```

それでは次に、上記の処理で出てきた`add_provider(provider)`を見ていきましょう。

#### Generator.add_provider() - Generatorにformatを追加する

https://github.com/joke2k/faker/blob/master/faker/generator.py#L22-L39

引数で渡されたProvider(ex. `<faker.providers.person.ja_JP.Provider>`)で定義されているpublicなmethodをGeneratorのformatに追加していってます。

#### Generator.set_formatter() - setattr()のラッパー関数

https://github.com/joke2k/faker/blob/master/faker/generator.py#L70-L75
`Generator.add_provider()`のところで急にformatという言葉が出てきましたが、ただGeneratorインスタンスに対してsetattr()を行っているだけです。

#### まとめ
ここまで見てきたように`Factory.create()`を行うことで、
各言語のProvider群で定義されているpublicなmethodを全てattributesにセットされた`<class 'faker.generator.Generator'>`のインスタンスが取得できるようになってます。
このおかげで、以下のように`fake.method_name()`と呼び出すだけで、各言語のProviderの中で実装されている`method_name()`が実行されてランダムなテストデータを取得することができてます。

```
>>> fake.name()
'Anfernee Reichel'
```

##さいごに
力尽きて`Factory.create()`の部分しか追えていませんが、Generatorの生成方法が理解できればこのライブラリの他の使い方もわかると思います。
こういった薄いライブラリを取り上げてのコードリーディングは、取っ付きやすく楽しかったのでオススメです！

##あとがき
この記事を書いている途中で、
『`ja_JP`の[PersonProvider](https://github.com/joke2k/faker/blob/master/faker/providers/person/ja_JP/__init__.py)は`name()`のformatを日本語で保持していたので、`user_name()`と`domain_word()`がきちんと表示されない』
という問題に遭遇しました。
https://github.com/joke2k/faker/blob/master/faker/providers/internet/__init__.py#L27-L32
https://github.com/joke2k/faker/blob/master/faker/providers/internet/__init__.py#L90-L95

上記の問題に対処するためのPRを出して、無事mergeしてくれました。
https://github.com/joke2k/faker/pull/300