Rails 中使用 Factory Girl 生成测试数据

简介

Rails 中对 Model 进行单元测试的时候,经常要使用一些测试用的伪数据,比如上文为测试 Post#prevPost#next 方法就手工创建了三个 Post 实例对象。

post1 = Post.create!(title: 'Post 1', slug: 'post-1', body: 'Post 1')post2 = Post.create!(title: 'Post 2', slug: 'post-2', body: 'Post 2')post3 = Post.create!(title: 'Post 3', slug: 'post-3', body: 'Post 3')

但是手工创建测试对象太麻烦了,因此可以使用 factory_girl 来为我们自动生成测试用的对象。

factory_girl_rails 这个 Gem 方便了在 Rails 中使用 factory_girl

安装

Gemfile 中添加以下内容,然后运行 bundle install

group :test do  gem 'factory_girl_rails'end

生成配置文件

用以下命令生成配置文件

rails g factory_girl:model model_name

所有的配置文件都放置于 spec/factories ( RSpec ) 或者 test/factories ( Test::Unit 或者 miniTest )

比如为 post 生成一个配置文件

rails g factory_girl:model post

将生成 spec/factories/posts.rb 文件。

编辑配置文件

打开这个文件,写入以下内容

FactoryGirl.define do  factory :post do    title 'A New Post'    slug 'a-new-post'    body title  endend

然后在 model 测试文件中就可以用 FactoryGirl.create(:post) 来生成测试对象,而这个测试对象就是用上面文件中所定义的属性创建的,其中对象的 body 属性的值是复制的 title 的。

sequence

因为 Post model 中有验证 titleslug 的唯一性,所以第二次创建就不会成功了,因为每次创建的对象属性都是一样的。那么就需要每次创建的对象属性不一样,用 sequence 方法便可以解决这个问题。

修改上面的 facotories 文件

FactoryGirl.define do  factory :post do    sequence(:title) { |n| "This is post #{n}" }    sequence(:slug) { |n| "this-is-post-#{n}" }    sequence(:body) { |n| "This is body of post #{n}" }  endend

sequence 在每次 create 时生成一个数,并传递给 block, 然后将 block 的返回值作为它的参数的值(分别是 :title :slug :body)。因为这些数是递增的序列,因此每次调用 FactoryGirl.create 时返回的对象都是不相同的。

# 1000.times or 10000.times whatever100.times do  Post.create! FactoryGirl.create(:post)end

动态赋值

上例中使用了 3 次 sequence 方法,可以缩减为一个,而其余两个属性都可以从第一个属性中变化得到。

FactoryGirl.define do  factory :post do    sequence(:title) { |n| "This is post #{n}" }    slug { title.parametize }    body { title }  endend

sequence 生成 title 属性,而另外两个属性则从 title 属性得来。

注意不能直接使用 slug title.parametizebody title,因为 title 的值是直到使用了 FactoryGirl.create 时才能确定的,因此要将 title.parametizetitle 放在 block 中,这样使 slugbody 的值也在 FactoryGirl.create 时确定。

继承

factory 支持继承,内层 factory 继承外层 factory 的属性

FactoryGirl.define do  factory :post do    sequence(:title) { |n| "This is post #{n}" }    slug { title.parametize }    body { title }    factory :published_post do      status 'published'    end    factory :drafted_post do      status 'drafted'    end  endend

:published_post:drafted_post 继承了 :post:title :slug:post 属性,并各自定义了自己的 :status :drafted_post

Trait

我查了下词典,trait 是 特性,特点 的意思。在这里有点像 rails 中的 concernsruby 中的 module

FactoryGirl.define do  factory :post do    sequence(:title) { |n| "This is post #{n}" }    slug { title.parametize }    body { title }    trait :published do      status 'published'    end    trait :drafted do      status    trait :yesterday_created do      created_at 1.days.ago    end  endend

这样可以利用 trait 自由的构造我们想要的对象的 特性。

# 创建一个 body 格式为 markdown 的 published 的 postFactoryGirl.create! :post, :published, :markdown# body 格式为 orgFactoryGirl.create! :post, :published, :org, :yesterday_created

使用 trait 的好处是可以自由的选择对象的特性,而可以避免使用继承时出现的 :published_markdown_post:published_org_yesterday_created_post 甚至更长的 factory

association

在一个 factory 中可以调用另外一个 factory

FactoryGirl.define do  factory :post do    sequence(:title) { |n| "Post #{n}"}    sequence(:slug) { |n| "post-#{n}" }    body { "Title: #{title}\nSlug: #{slug}" }    user  endend

上例中,:post 中有一行 user,如果存在 factory :user 的话,将直接调用 FactoryGirl.create :user 作为 :postuser

但是实际上 :post 里存在的是 :author 因此可以改成这样

FactoryGirl.define do  factory :post do    sequence(:title) { |n| "Post #{n}"}    sequence(:slug) { |n| "post-#{n}" }    body { "Title: #{title}\nSlug: #{slug}" }    author  endend

可以在 spec/factories/users.rb 中为 :user 添加一个 alias 就行了,这样 author 就会用 :user 来创建了。

factory :user, aliases: [:author] doend
Rails 中使用 Factory Girl 生成测试数据

相关文章:

你感兴趣的文章:

标签云: