iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0
自我挑戰組

初級紅寶石魔法師心得分享。系列 第 15

D-15.Rspec 從零開始寫測試(三) shoulda-matchers && Distribute Candies

今天簡單操作測試Associations

有能力用原生Rspec語法去測任何東西,一定超強的,但是為了快速了解這部分,請gem來幫助快速完成測試。


shoulda-matchers

Github:https://github.com/thoughtbot/shoulda-matchers
官網:https://matchers.shoulda.io/
官網上示範了,有無使用此gem需要寫的code有差多少。

安裝
Gemfile

group :development, :test do
  gem "shoulda-matchers"
end
#放test即可,但我還是把它跟昨天裝的gem放一起。
$ bundle install

其Github上說明,使用rails需在spec/rails_helper.rb底下寫入此段程式。我們就先用最簡單方式處理。

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

先測一個最簡單的have_many
此文畢竟是只是分享如何使用Rspec,就不對其他新增Class多做處理。並省略其測試檔案。不可取的行為

$ rails g model sword --no-test-framework
$ rails db:migrate

spec/models/role_spec.rb改下如下。

require 'rails_helper'

RSpec.describe Role, type: :model do
  let(:role) { create(:role) }

  describe 'associations' do
    it { should have_many(:swords) }
  end

  describe "測試驗證功能" do
  end
end

使用了shoulda-matchers就真的只需要簡短一句it { should have_many(:swords) }就可以檢測關聯性是否正確,但還是要說,有先了解過原本的怎麼寫會比較好。

理所當然這樣子直接檢測,只會得到一個紅燈,所以我們正確加上關聯性吧。
講個笑話:某人寫have_many(:sword),查失敗原因找了一個晚上


app/models/role.rb

class Role < ApplicationRecord
  has_many :swords
end

```app/models/sword.rb`

class Sword < ApplicationRecord
  belongs_to :role
end

rspec畫面

F......

Failures:

  1) Role associations is expected to have many swords
     Failure/Error: it { should have_many(:swords) }
       Expected Role to have a has_many association called swords (Sword does not have a role_id foreign key.)
  略....

當然錯誤的,正確應該幫swordtable加上role_id這個欄位,才能正確調動關聯性,所以不用擔心只有一句短短的it { should have_many(:swords) }會讓測試水準下降。

接著完成加入欄位動作吧。

$ rails g migration add_column_swords

#migrate
class AddColumnSwords < ActiveRecord::Migration[6.1]
  def change
    add_column :swords , :role_id, :integer
  end
end

$ rails db:migrate

rspec畫面。

.......

Finished in 0.20472 seconds (files took 1.59 seconds to load)
7 examples, 0 failures

It work!!!!


接著秉著先寫測試再開發的原則,將我們想要建立的關聯性測試先寫上吧。
spec/models/role_spec.rb

require 'rails_helper'

RSpec.describe Role, type: :model do
  let(:role) { create(:role) }

  describe 'associations' do
    it { should have_many(:swords) }
    it { should have_many(:items).through(:boxes) }
    it { should belong_to(:user) }
    it { should have_one(:skill) }
  end

  #略
end

belongs_to

第一次使用factory_botshoulda應該這邊會卡住一下下,所以先寫這個。

$ rails g model user --no-test-framework
$ rails g migration role_add_column

#migrate
class RoleAddColumn < ActiveRecord::Migration[6.1]
  def change
    add_column :roles, :user_id, :integer
  end
end

$ rails db:migrate

User

class User < ApplicationRecord
  has_one :role
end

Rolse

class Role < ApplicationRecord
  belongs_to :user
  #略...
end

關聯性建好了,user_id欄位也加了,但是這樣跑rspec一定會失敗,因為沒有建立User物件來讓測試知道User是不是真的有Role,所以factory_bot的部分也要改寫。

spec/factorise/user.rb

FactoryBot.define do
  factory :user do
  end
end
#這樣就可以,因為我沒有建立其他欄位。

spec/factories/role.rb

FactoryBot.define do
  factory :role do
    user
    name { Faker::Name.first_name }
    job { Faker::Job.title }
    age { rand(5..130)}
  end
end


#如果只想建立一個factories.rb檔案。
FactoryBot.define do
  factory :user do
  end

  factory :role do
    user
    name { Faker::Name.first_name }
    job { Faker::Job.title }
    age { rand(5..130)}
  end
end

rspec

.F.F......

Failures:

  1) Role associations is expected to have many items through boxes
     Failure/Error: it { should have_many(:items).through(:boxes) }
       Expected Role to have a has_many association called items (no association called items)
     # ./spec/models/role_spec.rb:8:in `block (3 levels) in <top (required)>

  2) Role associations is expected to have one skill
     Failure/Error: it { should have_one(:skill) }
       Expected Role to have a has_one association called skill (no association called skill)
     # ./spec/models/role_spec.rb:10:in `block (3 levels) in <top (required)>

可以發現只剩兩種關聯測試了。


接著還是將我做的事寫上,但如果關聯性很熟,可以略過了。

多對多

$ rails g model item --no-test-framework
$ rails g model box role:references  item:references --no-test-framework
$ rails db:migrate

Box.Model

class Box < ApplicationRecord
  belongs_to :role
  belongs_to :item
end

Item.Model

class Item < ApplicationRecord
  has_many :boxes
  has_many :roles, through: :boxes
end

Role.Model

class Role < ApplicationRecord
  has_many :boxes
  has_many :items, through: :boxes
end

has_one

$ rails g model skill role_id:integer --no-test-framework
$ rails db:migrate

Skill.Model

class Skill < ApplicationRecord
  belongs_to :role
end

Role.Model

class Role < ApplicationRecord
  has_one :skill
end

rspec

..........

Finished in 0.2414 seconds (files took 1.69 seconds to load)
10 examples, 0 failures

https://ithelp.ithome.com.tw/upload/images/20210915/20135887wir9lSWwMq.png

雖然驗證跟關聯可能是最簡單的,測試中也是Model最好寫,但是透過兩個方便的gem可以讓測試更好寫~
shoulda_matchers除了關聯,驗證與contoller都提供了更便捷的語法。

今日的內容:https://github.com/nauosika/Rspec_test/tree/D15


今天的leetcode575.Distribute Candies
題目連結:https://leetcode.com/problems/distribute-candies/
題目重點:真糖果題,會分享這題只是因為這題題目很可愛。
除了看題目眼睛有點花
糖果有不同類型,1代表1號糖果,2代表2號糖果,以此類推。
愛麗絲能吃到最多的量是糖果們/2,又盡可能想吃到每種不同的糖,所以一定會吃到糖果們.uniq
如果今天糖果們.uniq大於糖果們/2,愛麗絲還是最多只能吃到糖果們/2
如果今天糖果們/2大於糖果們.uniq,愛麗絲又希望吃到糖果們.uniq
好可憐的愛麗絲,但是我們題目解完了。

# @param {Integer[]} candy_type
# @return {Integer}
def distribute_candies(candy_type)
  [candy_type.uniq.size, candy_type.size/2].min
end

愛麗絲,多運動跟好好刷牙,就可以吃所有的糖果了。


上一篇
D-16. Rspec 從零開始寫測試(二) factory_bot_rails && Largest Number At Least Twice of Others
下一篇
D-14.Rspec 從零開始寫測試(四) 私有方法測不測? && Maximum Product of Three Numbers
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言