Gem FactoryGirl 粗浅介绍

juanito · June 13, 2012 · Last by hww replied at February 05, 2016 · 14174 hits
Topic has been selected as the excellent topic by the admin.

写的有点赶,大家凑合著看,有问题去 factory_girl 的 github wiki 页面 了解更多...或是发问...

为神马不用假数据 (Why not fixtures?)

神马是假数据?

Fixtures are a way of organizing test data

一种组织管理你测试数据的方法。

假数据

  • 用 YAML 写成,不是 Ruby

首先 Rails 的 Fixture 是用 YAML 写成的,比 XML 简洁多拉,但还是不爽阿,咱项目都用 Ruby 写的,我全都要用 Ruby!!! 就有人就开始在 YAML 里面塞 Ruby 代码,使用了 YAML 的继承特性...很快你的 YAML 就一团混乱拉。

  • YAML 直接插入数据库,太粗暴了

直接硬上也不做个安全措施……这点在很多方面都不好的,你懂得,比如没有验证 (NO validations)...

  • 忽视 ActiveRecord,没有 callback

ActiveRecord 有一套自己的流程,直接把 YAML 插入数据库,

整个不把 ActiveRecord 放在眼里,当然也不能使用好用的回调函数 (callback) 拉...

如果想自己加入 callback,那又得在假数据里面塞 Ruby 代码。。。又变一团糟了。

好了,米国有一家 Thoughtbot 公司,里面一群帅哥

hot-geeks-thoughtbot

觉得 尼马,这太坑爹了 ,就“搞了一个 [工厂女孩][fg]”来解决假数据的问题...

为 Rails 安装工厂女孩

factory_girl_rails provides Rails integration for factory_girl. Automatic factory definition loading will be added.

首先打开你的 Gemfile 添加:

group :test, :development do
    gem 'factory_girl_rails'
end

bundle 一下就装好了,喔耶!

它主要的核心功能是从 factory_girl 这个 Gem 来的。

目前 2012 年 6 月 12 日最新版本是 3.4.0

创建工厂

好了,安装好了,让我们看看怎么创建工厂哈...

先建立个 model 当例子

rails g model guitar description:string year:string strings:integer lefty:boolean

一个吉他模型,有著吉他的描述、年份、是几弦 (strings) 的 左手吉他 (lefty)...

invoke active_record create db/migrate/20120613012531_create_guitars.rb create app/models/guitar.rb invoke test_unit create test/unit/guitar_test.rb invoke factory_girl create test/factories/guitars.rb

刚刚我们在 Gemfile 把 factory_girl_rails 加在 :development 分组,

这样之后只要用到 rails g model 命令,就会自动替我们创建工厂:

create test/factories/guitars.rb

喔耶!接下来让我们迁移一下数据库:

rake db:migrate db:test:prepare

喔耶!迁移成功:

== CreateGuitars: migrating ================================================== -- create_table(:guitars) -> 0.0024s == CreateGuitars: migrated (0.0025s) =========================================

接下来打开 test/factories/guitars.rb

# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do factory :guitar do description "MyString" year "MyString" strings 1 lefty false end end

factory_girl gem 帮我们照刚刚产生模型那行指令建立了一个工厂...

我们生产吉他的工厂建好了...

现在让我们建立一下吉他的 model...

rails g model guitar description:string year:string strings:integer lefty:boolean

接下来让我们打开 console 在测试数据库里面玩弄一下工厂女孩 这个 gem...

rails c test

弄一支新吉他来玩玩

guitar = FactoryGirl.create(:guitar)

会看到:

(0.2ms) begin transaction SQL (1.2ms) INSERT INTO "guitars" ("created_at", "description", "lefty", "strings", "updated_at", "year") VALUES (?, ?, ?, ?, ?, ?) [["created_at", Wed, 13 Jun 2012 01:34:05 UTC +00:00], ["description", "MyString"], ["lefty", false], ["strings", 1], ["updated_at", Wed, 13 Jun 2012 01:34:05 UTC +00:00], ["year", "MyString"]]

(1.4ms) commit transaction => #

我们创了一把吉他出来,注意 SQL 是用 INSERT INTO ...

如果我们只是想创建一个吉他,但暂时不要建立一个记录,那可以:

guitar = FactoryGirl.build(:guitar)

我们就可以把玩各式各样 guitar 所有的方法啦...

guitar.new_record? => true

如果要存入数据库也是很简单:

guitar.save

TIPS: attributes_for 方法会针对给定的工厂返回一个 hash,这在功能及控制器测试里很有用。

attrs = FactoryGirl.attributes_for(:guitar) => {:description=>"MyString", :year=>"MyString", :strings=>1, :lefty=>false}

继承 (Inheritance)

让我们看看怎么用继承来让代码 DRY

假如我们有两种吉他,一个叫做 coldplay_lefty 左手吉他,coldplay_lefty 是六弦的,

年份 2000,另一个叫做 placebo,年份 2010,我们要怎么样利用继承来尽量不重复代码呢:

# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do factory :guitar do description "Basic Guitar" year "2012" strings 7 lefty false

factory :coldplay_lefty do description 'coldplay' year '2000' strings 6 lefty true end

factory :placebo do description 'placebo' year '2010' end end end

接下来重启 console 来试试看:

rails c test placebo = FactoryGirl.create(:placebo) coldplay_lefty = FactoryGirl.create(:coldplay_lefty)

关系 Relationships

好了,现在我们有俩店面,有的吉他是从 A 店面卖的,有的从 B 店面卖的,咋整呢?

产生一个店面 model 来存放吉他...一样 factory_girl 会替我们建立好工厂...

$ rails g model store_location address:string invoke active_record create db/migrate/20120613020928_create_store_locations.rb create app/models/store_location.rb invoke test_unit create test/unit/store_location_test.rb invoke factory_girl create test/factories/store_locations.rb

接著将两个表关连起来,给 Guitar 表加个 Foreign key...

rails g migration AddStoreLocationIdToGuitars store_location_id:integer

准备好数据库

rake db:migrate db:test:prepare

app/models/guitar.rb 添加两者的关系:

belongs_to store_location

test/factories/guitars.rb 添加 store_location

# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do factory :guitar do description "Basic Guitar" year "2012" strings 7 lefty false store_location

factory :coldplay_lefty do description 'coldplay' year '2000' strings 6 lefty true end

factory :placebo do description 'placebo' year '2010' end end end

重启 console 试试:

rails c test FactoryGirl.create(:guitar)

成功的话会发现两个表已经关联起来了,看看有没有 store_location_id 这个...

添加特定吉他销售地 test/factories/store_locations.rb :

# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do factory :store_location do address "My String" end

factory :coldplay_showroom, class: 'StoreLocation' do address "UK London coldplay" end

factory :placebo_showroom, class: 'StoreLocation' do address "UK Oxford placebo" end end

注意要加上 class: 'StoreLocation' 告诉它这是给 StoreLocation 用的工厂...

test/factories/guitars.rb 添加特定 store_location

# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do factory :guitar do description "Basic Guitar" year "2012" strings 7 lefty false store_location

factory :coldplay_lefty do description 'coldplay' year '2000' strings 6 lefty true store_location factory: :coldplay_showroom end

factory :placebo do description 'placebo' year '2010' store_location factory: :placebo_showroom end end end

序列 (Sequences)

If you need uniqueness on a field, sequencing comes in handy. The sequences can then be used in your other definitions. E.g. email

好,现在帮所有生产的吉他标个号。。。

rails g migration AddInventoryCodeToGuitars inventory_code:string

invoke active_record create db/migrate/20120613023044_add_inventory_code_to_guitars.rb

准备数据库...

rake db:migrate db:test:prepare == AddInventoryCodeToGuitars: migrating ====================================== -- add_column(:guitars, :inventory_code, :string) -> 0.0013s == AddInventoryCodeToGuitars: migrated (0.0015s) =============================

test/factories/guitars.rb 添加一个 sequence

sequence :inventory_code do |n| "GR#{n}" end

这样用:

inventory_code { FactoryGirl.next(:inventroy_code)}

整个文件看起来:

# Read about factories at https://github.com/thoughtbot/factory_girl

FactoryGirl.define do

sequence :inventory_code do |n| "GR#{n}" end

factory :guitar do description "Basic Guitar" year "2012" strings 7 lefty false store_location inventory_code { FactoryGirl.generate(:inventory_code)}

factory :coldplay_lefty do description 'coldplay' year '2000' strings 6 lefty true store_location factory: :coldplay_showroom end

factory :placebo do description 'placebo' year '2010' store_location factory: :placebo_showroom end end end

也可以一行搞定:

sequence(:inventory_code) { |n| "GR#{n}" }

...希望你大概了解了怎么用工厂女孩,更多信息上 github wiki 看...

factory_girl WIKI

[fg]: https://github.com/thoughtbot/factory_girl

@huacnlee li 的 字号貌似没有更新到 14...

@Juanito 故意的,这样有层次感一些

谢谢,写的真好。尤其是 玩弄 一词。:-)

哈哈,我喜欢你的文章.... 幽默风趣,又不失内容... :D

我喜欢这种文章

楼主总是乐于分享各种知识,超赞!!

#3 楼 @chucai 你们把楼主的文章当黄色小说看哇啊啊 啊

我对效率有点好奇,之前面试有人说测试数据的导入很费时,因为 ActiveRecord 太慢了. YAML 直接入数据库是否比较快呢

FactoryGirl 对我们的项目,帮助很大, 起初我们一直在使用 Fixtures,用其建立了,将近上百测试数据。 但是维护成本极高,极度混乱。

后来在社区同志们的引导下,我们引入了 FactoryGirl。 修改了所有测试代码,整个过程将近一个月的时间。 但是,从此这后,我们的世界清静了。

FactoryGirl 和 Fixtures 的思想不同,这导致我们需要重构思考测试方式。

我认为 FactoryGirl 强调动态, 当你需要测速数据的时候,再创建数据, 它提供了一个非常方便的机制, 让你非常容易的创建任何数据, 即便数据之间有很强的依赖关系。

Fixtures 强调静态, 事先把所有测试需要的数据准备好, 类似于一个数据库镜像, 我觉得这个思想也很好,但问题是,当数据量非常庞大的时候, Fixtures 并没有提供一种有效的维护方法, 导致维护成本颇高。

看到,楼主列出这么多帅哥的照片, 让人基情澎湃,有木有

学习了。FactoryGirl 你说那些家伙怎么会想出这么个名字的呢?

I prefer Fabrication over FactoryGirl

why?

  1. More sexy syntax
  2. Support multi ORMS, include mongoid
  3. I hate thoughtbot

#9 楼 @ery 说的极是,fixture 也有个旁门,就是导出一个现有数据库来做测试

#11 楼 @lilu

+1 Fabrication, 开发 Fabrication 的也是个帅小伙儿啊

为什么 hate thoughtbot?

#8 楼 @benzhang 插入的这点时间相比整理和维护测试数据的 不值一提啊

@knwang fabrication 是你们公司的产品? 这里有个 vs, https://gist.github.com/1135434 Fab 还没用过,factorygirl 了解得多一点,应该足够用了

我最喜欢楼主的文章啦。

之前居然没有注意到,惭愧呀!!

#10 楼 @zlx_star 我觉得他们希望像生产商品一样,批量生产女孩……所以,起名叫 factory_girl……

#18 楼 @chairy11 factory girl 和 ruby tuesday 是 The Rolling Stones 专辑里面的两首歌

#11 楼 @lilu why hate thoughtbot?

文章内容很详尽哦,充分说明了 FactoryGril 是个好东西!

好长的帖子。

不错的文章 👍

喔耶!接下来让我们迁移一下数据库: rake db:migrate db:test:prepare

Rails 4.1 之前的版本需要手动执行 rake db:test:prepare 或 rake db:test:clone 命令,把开 发数据库的结构复制到测试数据库中。现在,只要执行迁移,Rails 就会自动为你执行这一 步。

xiaoronglv in Factory Girl Vs Fixture mention this topic. 03 Apr 10:57
You need to Sign in before reply, if you don't have an account, please Sign up first.