Day 23 介绍 FactoryBot Rails 及设定

该文章同步发布於:我的部落格

首先,我们一样先介绍基本的安装以及设定,当一切就绪的时候,写测试这件事情也就没有那麽复杂和麻烦了~

安装 factory_bot_rails

和 RSpec 一样,我们把 factory_bot_rails 放在 Gemfile:

group :development, :test do
  gem 'factory_bot_rails', '~> 6.2'
end

记得,在实作专案的时候都要记得放上版本号喔!

factory_bot_rails 本身其实会根据以下的路径自动载入:

factories.rb
test/factories.rb
spec/factories.rb
factories/*.rb
test/factories/*.rb
spec/factories/*.rb

但若是你有自己想要放置的档案位置的话呢?

你可以在 config/application.rb 或是 config/envrioments.rb 中加入以下的指令:

config.factory_bot.definition_file_paths = ["你的资料夹名称/factories"]

factory_bot_rails 在开发环境时会自动地帮你产生工厂来产生物件,若是你觉得很烦,你想要自己建立档案的话呢?

你可以把 factory_bot_rails 移出 :development 的 group 或是在设定中加入:

config.generators do |g|
  g.factory_bot false
end

接着我们还有一些基本的设定要做,我们可以开一个资料夹,路径是 spec/support/factory_bot.rb 然後在里面放入这段程序码,并确认他会被 rails_helper.rb 给 require

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

这段的目的应该不难理解,就是 include FactoryBot 的方法,让我们可以在 _spec.rb 的档案中写 FactoryBot 的方法喔!

制造工厂

FactoryBot 最大的目的就是制造假的实体让我们的测试可以通过,因为若是测试也需要把资料都存进资料库的话,专案完成的过程中就会浪费很多的硬碟空间,这件事情是没有必要发生的!

後面也会提到安装 Database_cleaner 在每次测试前都清空资料库确保每次的测试环境都是乾净的。

这边先示范一个最基本的制造工厂的程序码:

FactoryBot.define do
  factory :user do
    first_name { "John" }
    last_name  { "Doe" }
    admin { false }
  end
end

我们从第二行开始看, factory :user 的意思是,User 类别的工厂,这个 Model 有三种属性,分别是 first_name 以及 last_nameadmin

这样我们就可以在写测试的时候使用:

build(:user) / create(:user)
# 这样就可以建立 user 实体,也可以把它赋予在变数里面
robert = build(:user)
# 这样他本身就会有我们在工厂里面设定好的属性!
robert.first_name # "John"

我们也可以用不同的命名,但使用同样的类别:

FactoryBot.define do
  factory :admin, class: 'User' do
    first_name { "John" }
    last_name  { "Doe" }
    admin { false }
  end
end

这样的话就可以这样写:

build(:admin)

Sequence

除了刚刚提到基本属性的制造外,我们常常会有不能重复的属性,像是 email 这样子的属性!

这时候我们就可以使用 sequence 来制作独特的属性,使用方法是:

FactoryBot.define do
  factory :user do
    first_name { "John" }
    last_name  { "Doe" }
    admin { false }
    email { generate(:email) }
    
    sequence(:email) { |n| "person#{n}@example.com" }
  end
end

接着我们可以制造几个实体并且印出来看看!

person_1 = build(:user)
person_2 = build(:user)

person_1.email # "[email protected]"
person_2.email # "[email protected]"

那个 n 会自动地帮我们递增,也顺便让这个值成为 unique,当然 sequence 有很多的特性和缩写,短短一篇文章也很难交代完整。

所以尽量以基本的使用方法来介绍~

build & create 的不同

这两种方式都可以制造出物件的实体,但最大的不同就在於有没有被 save,那至於有没有 save 有什麽关系吗?

有的,如果没有 save 的话,在 Feature test 就会找不到画面的显示,因为该物件根本没有被储存,所以不会出现在画面上。

其实只要想像到底会不会需要物件的 id 或是关联需要 id 的?

如果需要,请用 create 来制造物件!

若是我想要一次制造很多的物件呢?

FactoryBot 也有提供了 create_list / build_list 的方法,可以这样使用:

create_list(:user, 3) # save 3 个 user
build_list(:user, 3)

至於後面的数字就是你需要几个的意思!

Association

在写 Rails 时,我们常常会需要很多的关联,在测试时也不例外。

一开始我在学习使用 FactoryBot 的时候感到很复杂,也很困扰,但搞懂之後就觉得根本没有那麽困难~

我们先假设一间商店有很多的汉堡,这时候我们的工厂该怎麽写呢?

FactoryBot.define do
  factory :burger do
    association :store, factory: :store
  end
end

# 也可以缩写成这样

FactoryBot.define do
  factory :burger do
    store
  end
end

首先是 association :store 这件事,就是建立关联,接着我们告诉 FactoryBot 去哪找这个关联,factory: :store 这间工厂!

这样写的关联就是 Store has_many :burgers 的意思,因为身上会有 store_id 的表格应该是 Burger 才对!

After Create Hooks

今天的情境是,我们想要一间商店在 create 之後,就自动拥有 3 个汉堡。

一般来说在测试里面,你可能会这样写:

let(:store) { create(:store) }

before do 
  create(:burger, store: store)
  create(:burger, store: store)
  create(:burger, store: store)
end

OK,这或许没有什麽问题,也可以达到你的目的,但当测试档案变大的时候,就会很难看懂,因为太多这样的 before do end 的情形了

所以我们希望这个 store 在建立起来的时候就有汉堡了!

我们可以在工厂里面先订立好规则:

FactoryBot.define do
  factory :store do
    ...
    
    after :create do |store|
      create_list(:burger, 3,  store: store)
    end
  end
end

这段 Code 的意思就是在 create 的瞬间帮我产生 3 个汉堡~

这样我们就可以让 _spec.rb 的档案很乾净,很清晰!

Traits

这是我个人觉得超级好用的功能,可以让你帮同一个物件贴上不同的标签。

什麽意思呢?

假设我们的 User 有不同的角色,有管理员,有一般人,这时候原本的我会这样写:

robert = create(:user)
robert.role = "Admin"
....

利用後来覆写的方式来改变状态,但其实我们可以不用这样做,有超级方便的方法让我们来使用!

我们一样在工厂里面做好设定:

FactoryBot.define do
  factory :user do
    ...
    
    traits :admin do
      role { 'Admin' }
    end
  end
end

我们可以想像在工厂里面已经有一张贴纸叫做 :admin,接着我们可以这样用:

robert = create(:user, :admin)
robert.role # 'Admin'

是不是超级好用的,我们就可以把各式各样的 traits 先建立起来,用於应付各式各样的测试环境喔!

安装 DatabaseCleaner

刚刚有提过,我们希望在每次测试的开始前都有乾净的环境可以测试,所以我们也要在环境中加入这个 Gem。

我们依照手册来安装:

# Gemfile
group :test do
  gem 'database_cleaner-active_record', '~> 2.0', '>= 2.0.1'
end

一样在 spec_helper.rb 中加入:

RSpec.configure do |config|

  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end

end

可以看到他在每一次的测时前都会执行这些指令唷!

关於 transaction 以及 truncation 在这个套件的差别:

transaction: rollback 的方式
truncation: 清除资料库的所有资料

小结

FactoryBot 的使用方式实在是太多了,就像写测试一样,你问一百个人会有一百种写法~

我真的没办法一一详细介绍到每个方法,但这些方法对我来说就很够用了,甚至你也可以使用 traits 搭配 after_create hooks 来制造标签以达到很多种不一样的效果。

如果真的很闲可以看 官方文件,但我都是想到实作某种方式才去查我需要的东西。

明天会介绍另一个也很有趣的套件 Capybara ~


<<:  Golang 学习笔记-- 快速上手/重点整理 - 1

>>:  Day 25 - 模板

[Day 16] 实作-图片轮播 Carousels

今天来实作首页的活动图片轮播, 先介绍这次会用到 Vuetify 的 Carousels 组件 Ca...

Gulp Babel ES6 编译(1) DAY83

Babel 为编译 ES6 的工具 那我们就要开始介绍如何安装与使用啦 https://www.np...

Day10-D3 Transition 动画

本篇大纲:transition( ) 移动、变换颜色、transition.delay( )、tr...

自动化测试,让你上班拥有一杯咖啡的时间 | Day 13 - 动态跳过测试用例

此系列文章会同步发文到个人部落格,有兴趣的读者可以前往观看喔。 测试脚本中有许多测试用例时,当需要...

小工厂大经验

工作桌会随着行业别的不同,而有不同的设置和摆放,最多的摆设就是个人电脑周边,以及电话机,还有可卸式O...