世界一やさしいFactoryGirlの使い方:応用編

今回は **「もっと！FactoryGirl<span style="color: #ff0000">♡</span>」**をテーマに、

- 連番のデータ
- リアルなダミーデータ
- 関連モデルのテストデータ

以上３種類のテストデータをモリモリ作っていこうと思います。

FactoryGirlの導入方法や、基本的な使い方に関しては
こちらの記事を見ていただけると嬉しいです⊱ฅ•ω•ฅ⊰   
はてなブログ: [新人よう太の冒険： FactoryGirlとの出会い ~序章~](http://neo-developers.hatenablog.com/entry/2017/01/12/113112)
Qiita: [世界一やさしいFactoryGirlの使い方:入門編](http://qiita.com/9_tricks/items/f333a76bcf7f90f85144)


### テーブル構成<span style="color: #ff0000">♡</span>
今回は **「関連モデルのテストデータを作成したい...｣** という衝動に駆られたため、
中間テーブルを含む、3つのテーブルを用意しました。

| engineer| purchase_history|cake |
|:-----------:|:------------:|:------------:|
| id     |        id |     id     |
|name|engineer_id|name|
|employee_number|cake_id|price|
|department_name|||

「engineer」テーブルは、エンジニアの名前、社員番号、部署名、
「cake」テーブルは、ケーキの名前、価格をそれぞれ格納します。
「purchase_history」テーブルは、
「engineer」テーブルと「cake」テーブルの中間テーブルとなっています。

### <a name="a">連番のテストデータを作成する</a><span style="color: #ff0000">♡
</span>エンジニアの社員番号(employee_number)は、
各社員に1つずつ与えられている一意な番号が入るため、
テストの際は社員番号が重複しないテストデータを作成する必要があります。
連番(sequence)のテストデータであればデータが重複する心配がないので、
早速作ってみようと思います。

#### 1から順番にテストデータを作成したい場合
Factorygirlの定義を、以下のように記述すると  

``` ruby 
 # :keyには「キー名」が入ります！  
sequence(:key) { |n| n }
```

「1」から始まる連番のテストデータが作成されます。

```ruby
FactoryGirl.define do
  factory :engineer do
    name "Mytext"
    sequence(:employee_number) { |n| n }
    department_name "Mytext"
  end
end
```

試しにtest環境のDBを覗いてみると....

```sql
mysql> select * from engineers;
+----+--------+--------+---------+---------------------+---------------------+
| id | name   | employee_number | department_name | created_at          | updated_at          |
+----+--------+--------+---------+---------------------+---------------------+
|  1 | Mytext | 1      | Mytext  | 2017-01-30 04:52:05 | 2017-01-30 04:52:05 |
|  2 | Mytext | 2      | Mytext  | 2017-01-30 04:52:05 | 2017-01-30 04:52:05 |
|  3 | Mytext | 3      | Mytext  | 2017-01-30 04:52:05 | 2017-01-30 04:52:05 |
|  4 | Mytext | 4      | Mytext  | 2017-01-30 04:52:05 | 2017-01-30 04:52:05 |
|  5 | Mytext | 5      | Mytext  | 2017-01-30 04:52:05 | 2017-01-30 04:52:05 |
+----+--------+--------+---------+---------------------+---------------------+
```
employee_numberが、「1」から始まる連番のテストデータとして作成されています！

#### 初期値を設定したい場合
マンションやアパートの部屋番号は<span style="color: #0000cc">3桁</span>以上の数字が指定されていることがあります。
このような「<span style="color: #0000cc">n桁</span>以上のデータ」がテスト対象である場合、
前述の方法で作成すると必ず<span style="color: #0000cc">1桁</span>から始まるのでテストデータとして正しいとは言えません。
以下のように `:key`の後に、初期値を加えると....

```ruby 

#:keyには「キー名」が入ります！ 
sequence(:key,100) { |n| n }
```
 
```sql
mysql> select * from engineers;
+----+--------+--------+---------+---------------------+---------------------+
| id | name   | employee_number | department_name | created_at          | updated_at          |
+----+--------+--------+---------+---------------------+---------------------+
|  1 | Mytext | 100    | Mytext  | 2017-01-30 05:27:31 | 2017-01-30 05:27:31 |
|  2 | Mytext | 101    | Mytext  | 2017-01-30 05:27:31 | 2017-01-30 05:27:31 |
|  3 | Mytext | 102    | Mytext  | 2017-01-30 05:27:31 | 2017-01-30 05:27:31 |
|  4 | Mytext | 103    | Mytext  | 2017-01-30 05:27:31 | 2017-01-30 05:27:31 |
|  5 | Mytext | 104    | Mytext  | 2017-01-30 05:27:31 | 2017-01-30 05:27:31 |
+----+--------+--------+---------+---------------------+---------------------+
```

初期値が「100」の連番データを作成することができます！


### リアルなダミーデータ<span style="color: #ff0000">♡</span>

``` ruby
FactoryGirl.define do
  factory :engineer do
    name "Mytext"
    sequence(:employee_number) { |n| n }
    department_name "Mytext"
  end
end

```

Factoryの定義がデフォルトのままだと、エンジニアの名前は「"Mytext"」になってしまいます。
エンジニアの名前を任意のテストデータに上書きしてもいいのですが、
複数のテストデータを生成した場合、
エンジニアの名前がすべて「 **マリー・アントワネット** (任意のテストデータ)」になってしまいます。  

```sql
mysql> select * from engineers;
+----+-----------------------------------+--------+---------+---------------------+---------------------+
| id | name                              | employee_number | department_name | created_at          | updated_at          |
+----+-----------------------------------+--------+---------+---------------------+---------------------+
|  1 | マリー・アントワネット            | 1      | Mytext  | 2017-01-30 01:08:29 | 2017-01-30 01:08:29 |
|  2 | マリー・アントワネット            | 2      | Mytext  | 2017-01-30 01:08:29 | 2017-01-30 01:08:29 |
|  3 | マリー・アントワネット            | 3      | Mytext  | 2017-01-30 01:08:29 | 2017-01-30 01:08:29 |
|  4 | マリー・アントワネット            | 4      | Mytext  | 2017-01-30 01:08:29 | 2017-01-30 01:08:29 |
|  5 | マリー・アントワネット            | 5      | Mytext  | 2017-01-30 01:08:29 | 2017-01-30 01:08:29 |
+----+-----------------------------------+--------+---------+---------------------+---------------------+
``` 

今回は「エンジニアの名前」にユニークキーを貼っていないので、
名前が重複していても問題はないのですが、
せっかくなのでFakerを使用してテストデータを作ってみようと思います。  

#### Fakerとは?
[Faker](https://github.com/stympy/faker)は現実世界にありそうな名前や単語をランダムに作成してくれる便利なgemです。
人名以外にも、電話番号や日時、住所、食べ物など様々なテストデータが用意されており、
Factoryの定義に使いたいテストデータを追加するだけで、
簡単にランダムなテストデータを生成することができます。

```ruby
FactoryGirl.define do
  factory :engineer do
    name Faker::Name.name 
    sequence(:employee_number) { |n| n }
    department_name "Mytext"
  end
end
```

これでテストを実行するたびに「エンジニアの名前」が変更されるようになりますが、
この書き方だと、一度に複数のテストデータを作成した場合、
 **マリー・アントワネット現象** (すべて同じ名前が生成される)が発生します。  

複数のテストデータを生成する際に、
<a href="#a">【連番のテストデータを作成する】</a>で紹介した「sequence」を使用すると、  
テストデータがランダムに生成されます。

``` ruby
FactoryGirl.define do
  factory :engineer do
    sequence(:name) {Faker::Name.name}
```

```sql
mysql> select * from engineers;
+----+-------------------+--------+---------+---------------------+---------------------+
| id | name              | employee_number | department_name | created_at          | updated_at          |
+----+-------------------+--------+---------+---------------------+---------------------+
|  1 | Hubert Ernser     | 1      | Mytext  | 2017-01-30 02:05:44 | 2017-01-30 02:05:44 |
|  2 | Gust Lowe         | 2      | Mytext  | 2017-01-30 02:05:44 | 2017-01-30 02:05:44 |
|  3 | Mr. Kory Sawayn   | 3      | Mytext  | 2017-01-30 02:05:44 | 2017-01-30 02:05:44 |
|  4 | Mr. Gus Towne     | 4      | Mytext  | 2017-01-30 02:05:44 | 2017-01-30 02:05:44 |
|  5 | Dillan Powlowski  | 5      | Mytext  | 2017-01-30 02:05:44 | 2017-01-30 02:05:44 |
+----+-------------------+--------+---------+---------------------+---------------------+
```

Fakerで用意されているテストデータは、すべて英語で構成されています。
**[faker-japanese](https://github.com/tily/ruby-faker-japanese)**というgemを使用すると、日本語のテストデータを作成することができます。
現在は日本人の「名前」のみの対応であるため、
名前のテストデータ生成は **「faker-japanese」**
その他のテストデータ生成は **「Faker」** 
上記のように使い分けると良いかもしれません。 
(個人的見解です。)


Fakerの[公式ドキュメント](https://github.com/stympy/faker)に、テストデータの種類と使い方が掲載されているので
ぜひチェックして下さい╭( ・ㅂ・)و̑ ｸﾞｯ  

 **「ポケモン」「ビール」「StarWars」のテストデータもありますよ！**
.....「ビールのイースト」のテストデータっていつ使うんだろう.......

### 関連モデルのテストデータを作成する<span style="color: #ff0000">♡</span>
今回作成したテーブルは、

 - 「engineer」テーブル
 - 「cake」テーブル
 - 「purchase_history」テーブル
   
   
 の３種類でした。  
「engineer」テーブルは複数の「cake」を、「cake」テーブルは複数の「engineer」と紐づいており、
「purchase_history」テーブルは、この二つのテーブルを繋ぐ中間テーブルとなっています。

#### has_many編
モデルで「<b>has_many through</b>」を定義している場合、
Factoryに定義を追加すると、
関連モデルのテストデータを同時に生成することができます。

``` ruby
FactoryGirl.define do
  factory :engineer do
    sequence(:name) {Faker::Name.name}
    # numerもFakerを使うようにしました。
    sequence(:number) {Faker::Number.number(4)}
    # 部署名はランダムに振り分けることにしました。
    sequence(:section) { ["インフラ部","フロントエンド部","デザイン部"].sample }

    after(:build) do |engineer|
      engineer.cakes << FactoryGirl.build(:cake)
    end
  end
end
```

「engineer」モデルのFactoryに追加したのは、
「engineer」と<b>has_many through</b>の関係にある「cake」モデルのFactoryです。
 after(:build)で「engineer」のテストデータが生成された後に、
「cake」モデルのテストデータを生成するように定義しています。
 また、中間テーブルである「purchase_history」のテストデータも同時に生成されます。
 
▼「engineer」モデルの作成

``` ruby
engineer = FactoryGirl.create(:engineer)
```

▼「engineer」のレコード

``` ruby
p engineer
  # => <Engineer id: 1, name: "Bethel Dare", employee_number: "1552", department_name: "インフラ部", created_at: "2017-01-30 17:02:11", updated_at: "2017-01-30 17:02:11">

```

▼「engineer」モデルに紐づく「cake」

``` ruby
p engineer.cakes
  # => <ActiveRecord::Associations::CollectionProxy [#<Cake id: 1, name: "MyString", price: 517, created_at: "2017-01-30 17:02:11", updated_at: "2017-01-30 17:02:11">]>
```
  
▼「engineer」モデルに紐づく 「purchase_history」

``` ruby
p engineer.purchase_histories
  # => <ActiveRecord::Associations::CollectionProxy [#<PurchaseHistory id: 1, engineer_id: 1, cake_id: 1, created_at: "2017-01-30 17:02:11", updated_at: "2017-01-30 17:02:11">]>
```



上記のように、 **engineer.cakes** と指定すると、
「engineer」モデルに紐づく「cake」モデルのテストデータを、

**engineer.relations_cakes** と指定すると、
「engineer」モデルに紐づく「purchase_history」モデルのテストデータを取得することができます。

「engineer」モデルのid、「cake」モデルのid、
「purchase_history」モデルのengineer_idとcake_idを見てみると、
それぞれの値が一致していることがわかります。

#### belogs_to編
モデルで「<b>belongs_to</b>」を定義している場合、
Factoryにafter(:build)などの<a href="#callback">コールバック</a>処理を記述することで、
関連モデルのテストデータを同時に生成することができます。 

``` ruby
FactoryGirl.define do
  factory :purchase_history do
    sequence(:engineer_id) {Faker::Number.number(4)}
    sequence(:cake_id) {Faker::Number.number(4)}
    
    after(:build) do |purchase_history|
      purchase_history.engineer << FactoryGirl.build(:engineer,id: purchase_history.engineer_id)
      purchase_history.cake << FactoryGirl.build(:cake,id: purchase_history.cake_id)
    end
  end
end
```

「purchase_history」モデルのFactoryに追加したのは、
「<b>belongs_to</b>」の関係にある「engineer」モデルと「cake」モデルのFactoryです。
 after(:build)で、「purchase_history」のテストデータが生成された後に、
「engineer」モデルと「cake」モデルのテストデータを生成するように定義しています。  
 
 
▼「purchase_history」モデルの作成

```ruby
purchase_history = FactoryGirl.create(:purchase_history)
```

▼「purchase_history」のレコード

``` ruby
p purchase_history
# => <PurchaseHistory id: 3, engineer_id: 8146, cake_id: 7019, created_at: "2017-01-31 02:29:10", updated_at: "2017-01-31 02:29:10">
```

▼「purchase_history」モデルに紐づく「engineer」

``` ruby
p purchase_history.engineer
# => <Engineer id: 8146, name: "Arthur Paucek", employee_number: "5403", department_name: "インフラ部", created_at: "2017-01-31 02:29:10", updated_at: "2017-01-31 02:29:10">
```


▼「purchase_history」モデルに紐づく「cake」

``` ruby
p purchase_history.cake
# => <Cake id: 7019, name: "MyString", price: 741, created_at: "2017-01-31 02:29:10", updated_at: "2017-01-31 02:29:10">
```

上記のように、<b>purchase_history.engineer</b> と指定すると、
「purchase_history」モデルに紐づく「cakes」モデルのテストデータを、
<b>purchase_history.cake</b>と指定すると、
「purchase_history」モデルに紐づく「cakes」モデルのテストデータを取得することができます。

こちらも、「engineer」モデルのid、「cakes」モデルのid、
「purchase_history」モデルのengineer_idとcake_idを見てみると、
それぞれの値が一致していることがわかります。


#### <a name="callback">コールバック</a>
関連モデルのデータを作成する際に、`after(build)`を使用しています。

```ruby
after(:build) do |purchase_history|
  purchase_history.engineer << FactoryGirl.build(:engineer,id: purchase_history.engineer_id)
  purchase_history.cake << FactoryGirl.build(:cake,id: purchase_history.cake_id)
end
```

コールバックとは、「処理(A)を行う前後で、処理(B)を行うこと」です。  
  
「purchase_history」モデルのFactoryに置き換えてみると、
処理(A)には
FactoryGirl.create(:purchase_history)
処理(B)には
FactoryGirl.build(:engineer,id: evaluator.engineer_id),
FactoryGirl.build(:cake,id: evaluator.cake_id)

上記の内容があてはめられます。

FactoryGirlには、以下のコールバックが存在します。

- after(:build)
- aftre(:create)
- before(:create)
- after(:stub)

#### transient
FactoryGirlには「transient(一時的な属性)」という機能があります。
`transient`を使うと、「条件によって作成するテストデータを分けるためのフラグ」など、
モデルに存在しない属性を定義することができます。


それでは、今回作成した「cake」モデルを使って`transient`の挙動を確かめてみたいと思います。

「cake」モデルは以下の属性を持っていました。

|cake |
|:------------:|
|     id     |
|name|
|price|


この「cake」モデルに対して、
 **is_used_chocolate**という「ケーキにチョコレートが使われているかどうか」を判断するフラグを、
`transient`を使って定義してみます。

```ruby
FactoryGirl.define do
  factory :purchase_history do
    sequence(:engineer_id) {Faker::Number.number(4)}
    sequence(:cake_id) {Faker::Number.number(4)}
     
    #一時的な属性
    transient do
      is_used_chocolate false
    end
      
    after(:build) do |purchase_history,evaluator|
      purchase_history.engineer = FactoryGirl.build(:engineer, id: evaluator.engineer_id)
      purchase_history.cake = FactoryGirl.build(:cake, id: evaluator.cake_id)

      purchase_history.cake.name.insert(0, "chocolate_") if evaluator.is_used_chocolate
    end
  end
end

```

チョコレートが使われているケーキは、そうでないケーキよりもカロリーが高いそうなので、
他のケーキと区別がつけやすいように **is_used_chocolate** がtrueであれば、
 **cake.name**の先頭に"chocolate_"を追加するようにしてみます。

```ruby
FactoryGirl.define do
  factory :cake do
    sequence(:name){ ["bush_de_noel","sachertorte","brownie"].sample }
    sequence(:price){ [*500..750].sample }
  end
end
```

Factoryを呼び出す際に、`transient`内で定義した属性を指定し、テストを実行すると...

```ruby
 # 今回はis_used_chocolateをtrueにしたかったので、値も指定しています。
 purchase_history = FactoryGirl.create(:purchase_history,is_used_chocolate: true)

 p purchase_history.cake
 => #<Cake id: 2845, name: "chocolate_sachertorte", price: 750, created_at: "2017-02-03 05:51:40", updated_at: "2017-02-03 05:51:40">
```

チョコレートがけられているケーキの名前に、"chocolate_"が追加されました！

コールバックを行う際に、`transient`内で定義した属性を使用する場合は、
コールバックに第2引数を追加する必要があります。

第2引数の中身を見てみると、
`transient`内の属性を含んだFactoryが入っていることが確認できます。

```ruby
p evaluator
=> #<#<Class:0x00000004e85fe0>:0x00000004e84f28 
@build_strategy=#<FactoryGirl::Strategy::Create:0x00000004e868a0>,
@overrides={:is_used_chocolate=>true, :engineer_id=>"4087", :cake_id=>"2845"},
@cached_attributes={:is_used_chocolate=>true, :engineer_id=>"4087", :cake_id=>"2845"}, 
@instance=#<PurchaseHistory id: nil, engineer_id: 4087, cake_id: 2845, created_at: nil, updated_at: nil>>
```

`transient`内の属性は第1引数には含まれないため、
`transient`を使ったテストを実行する際は、第2引数を指定してデータにアクセスしましょう！


#### Factoryで関連モデルを追加する際の注意点
これは基本的なことだと思うのですが、個人的に恐ろしく詰まったのでご紹介しようと思います。

Factoryで、「<b>has_many through</b>」の関係にあるモデルを追加する場合
 →「engineer.cakes」のように、追加したいモデル名を複数形にする
「<b>belogs_to</b>」の関係にあるモデルを追加する場合
 →「purchase_history.engineer」のように、追加したいモデル名を単数形にする


「<b>has_many</b>」と「<b>belongs_to</b>」の関係を考えれば、
それぞれに複数形/単数系が入ることがわかると思うのですが...
私は「<b>belogs_to</b>」に定義を追加する際にも複数形を使用してしまっていたので、
1時間ほど詰まってしまいました...
お気をつけください！

### 様々なテストデータを作ってみて<span style="color: #ff0000">♡</span>
ここまで読んでいただきありがとうございました。
今回は、以前書いた記事で取り上げていなかった

 - 連番のデータ  
 - リアルなダミーデータ  
 - 関連モデルのテストデータ  

以上3種類のテストデータを作成しました。

FactoryGirlを使ってテストデータを作成するようになって、約3ヶ月ほど経ったのですが、
「関連モデルのテストデータ」の作成方法を知ってから、
一気にテストコード作成の幅が広がったと感じています。
FactoryGirlをまだ使ったことが無い方、
テストコードを書きたいけれど面倒臭い...と感じている方は、
ぜひ一度FactoryGirlを使ってみてください！

### References
- [いまさら聞けないfactory_girl入門](http://tech.grooves.com/entry/2015/04/28/173025)
- [Factory Girl Railsのチートシート](http://ruby-rails.hatenadiary.com/entry/20150102/1420174315)
- [FactoryGirlのtransientとtraitを活用する](http://qiita.com/joker1007/items/da63b8630351c1f3fe1d)