iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
自我挑戰組

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

D-14.Rspec 從零開始寫測試(四) 私有方法測不測? && Maximum Product of Three Numbers

  • 分享至 

  • xImage
  •  

繼續把model的測試寫完

private方法屬於不直接測試,利用有用到它們的public方法測試即可。類別方法與實體方法都應該要測試。

我新增了一些測試,假設都是我想增加的功能。

require 'rails_helper'

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

  describe 'associations' do
  end

  describe "測試驗證功能" do
  end

  describe "#attack_power" do
  it "基本攻擊力等於力量*10"
  it "力量是6基本攻擊力為60"
  it "力量是4基本攻擊力為40"
  it "武器DPS是所持sword的dps"
  it "min = 2 , max = 4 , dps = 3"
  it "真正攻擊力等基本攻擊力*武器DPS"
  it "力量4 武器min:2, max:5 真正攻擊力120"
  end
end

為了有紀錄力量基本攻擊力真正攻擊力sword_dps,在Role加上這些些欄位。
Sword我們一樣假設測試過了。不良示範了

Role

$ rails g migration add_role_column_about_attack_power

class AddRoleColumnAboutAttackPower < ActiveRecord::Migration[6.1]
  def change
    add_column :roles, :power, :integer
    add_column :roles, :attack_power, :integer
    add_column :roles, :really_attack_power, :integer
    add_column :roles, :sword_dps, :integer
  end
end

$ rails db:migrate

#帥一點的指令。
$ rails g migration AddColumnsToRole power:integer attack_power:integer really_attack_power:integer sword_dps:integer
$ rails db:migrate

Sword

$ rails g migration add_column_sword_min_max_damge

class AddColumnSwordMinMaxDamge < ActiveRecord::Migration[6.1]
  def change
    add_column :swords, :min_damge, :integer
    add_column :swords, :max_damge, :integer
  end
end

$ rails db:migrate

Factory_bot當然需要修改。

spec/factories/role

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

    after :create do |role|
      create_list :sword, 3, role: role #has_many這樣建立,數字代表建立幾個。
      #create :sword, role: role  ##has_one這樣建立。
    end
  end
end

spec/factroies/sword

FactoryBot.define do
  factory :sword do
    role
    min_damge { 3 }
    max_damge { 6 }
  end
end

都是簡單計算,省略過程。

role.spec.rb

describe "#attack_power" do
    it "基本攻擊力等於力量*10" do
      expect(role.attack_power).to be(role.power * 10)
    end

    it "力量是6基本攻擊力為60" do
      role.power = 6
      expect(role.attack_power).to be(60)
    end

    it "力量是4基本攻擊力為40" do
      role.power = 4
      expect(role.attack_power).to be(40)
    end

    it "武器DPS是所持sword的dps" do
      expect(role.sword_dps).to be(role.swords.first.dps)
    end

    it "min = 2 , max = 4 , dps = 3" do
      role.swords.first.min_damge = 2
      role.swords.first.max_damge = 4
      expect(role.sword_dps).to be(3)
    end

    it "真正攻擊力等基本攻擊力*武器DPS" do
      expect(role.really_attack_power).to be(role.attack_power * role.sword_dps)
    end

    it "力量4 武器min:2, max:5 真正攻擊力120" do
      role.power = 4
      role.swords.first.min_damge = 2
      role.swords.first.max_damge = 5
      expect(role.really_attack_power).to be 120
    end
  end

role.rb

class Role < ApplicationRecord
  def attack_power
    power * 10
  end

  def really_attack_power
    attack_power * sword_dps
  end

  def sword_dps
    @sword = self.swords.first
    @sword.dps
  end
end

sword.rb

class Sword < ApplicationRecord
  #略...
  def dps
    (min_damge + min_damge) / 2
  end
end

到這邊rspec也沒有問題。


但假設role.rbsword_dps變成private

role.rb

private
def sword_dps
    @sword = swords.first
    @sword.dps
  end
end

rspec畫面會出現錯誤,都是跟private有關的。
內容都是找不到sword_dps這方法了。

.............FFF.

Failures:
NoMethodError:
       private method sword_dps called for #<Role:0x00007f814d0161b8>
#略...
rspec ./spec/models/role_spec.rb:62 # Role#attack_power 武器DPS是所持sword的dps
rspec ./spec/models/role_spec.rb:66 # Role#attack_power min = 2 , max = 4 , dps = 3
rspec ./spec/models/role_spec.rb:72 # Role#attack_power 真正攻擊力等基本攻擊力*武器DPS

噗噗,就算再簡單,花時間的測試,等於沒測了。


很多人會這樣說,私有方法不測試,應該更專注在你的公開方法上,因為要private就代表不是要直接給使用者使用,或許因為商業邏輯很複雜,或許其他Model也會一直用到這個方法,願意的話測試也可以,但最終只會刪除或變成無意義的註解,測試會變得沒有意義,心裡面安心而已。

真的要測可以利用send方法處理(callback部分有示範),或是像我這樣,測了再刪。
可以看到我最後一個測試就是有調動到私有方法的,所以我可以安心的把這三個測試刪除了(我在自己本機上是註解)。

這個部分並不是在分享該怎麼做
我也是Rspec新手,但若確定自己要建立私有方法,照大家說的私有方法不測試,那代表我一開始測試的內容就不該那樣設定。重點是怎麼規畫好測試流程。


Callback部分。

Callback的測試與私有方法很類似,就是測試有調動到Callback的方法就好。
而部分會用到Callback來執行的方法,可能都會成為private....
所以我今天的code到最後可能只會剩下下面那樣。

Role

  before_update :attack_power, :really_attack_power

rspec/model/role_spec.rb

describe "#call_back before_update" do
    it "素質更改後,攻擊力會更改" do
      create(
        :role,
        name: "測試用",
        job: "戰士",
        age: "29",
        power: "6",
        attack_power: nil,
        really_attack_power: nil
      )
      role.power = 5
      role.save
    expect(role.send(:attack_power)).to be(50)
    expect(role.send(:really_attack_power)).to be(200)
    end
  end

在我本機上,我是沒將這兩個方法設私有,雖然只是簡單的code要直接刪掉還真捨不得?
所以在設計時,就真的該想好哪些方法是private方法。

今日的檔案:https://github.com/nauosika/Rspec_test/tree/D14

Model雖然是最簡單的測試,但幫我自己紀錄一下怎麼開頭Rspec
關於分享測試就到今天結束了。


今天的leetcode.628:Maximum Product of Three Numbers
題目連結:https://leetcode.com/problems/maximum-product-of-three-numbers/
題目重點:考慮到負負得正,負負負會得負,正正負會得負,還有負數-1最大-1000最小。

#錯的
def maximum_product(nums)
  ans = 1
  new_arr = nums.max(3)
  new_arr.each do |num|
    ans *= num
  end
  ans
end

這是我一開始的解法,沒有考慮到值有超過三個時,兩個很大的負數相乘再乘一個最大的正數,才會是正解。
submit出去後發現還有一個例子。

[-100, -98, -1, 2, 3, 4]

但這個例子出現,其實也透露出答案了。
越小的負數,去掉負號後反而是最大的,以這個例子看就是前兩個與最後一個相乘最大。last * nums[0] * nums[1],如果都是正數那一定是max(3).reduce(:*),那就這兩個選大的。
不用去擔心陣列中數字很多,數字越多,這種狀況反而會越明顯。

def maximum_product(nums)
  nums.sort!
  [nums.max(3).reduce(:*), nums.last * nums[0] * nums[1]].max
end

這樣就解完了。


上一篇
D-15.Rspec 從零開始寫測試(三) shoulda-matchers && Distribute Candies
下一篇
D-13, Ruby 正規表達式(一) Regexp && Valid Palindrome
系列文
初級紅寶石魔法師心得分享。30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言