D-14.Rspec 从零开始写测试(四) 私有方法测不测? && Maximum Product of Three Numbers

继续把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

这样就解完了。


<<:  [DAY 01]天啊我为什麽要建这chatbot

>>:  Day 11 运算宝石:EC2 储存资源 EBS Types 方案比较

背包系统详解

//装备药水:实现药水单向(若装备栏内没药水可直接装备)、双向置换(若装备栏内已经有药水,直接点击新...

javascript变数与运算子3

接下来介绍比较运算值,也是用程序码跟注解来呈现 结果会以布林true跟false显示 ...

【面试】自我介绍要点

个人比较喜欢的流程是: 对方自我介绍、讲一下面试流程 让我自我介绍 ... 有些工程师可能很忙,面试...

Day09 建造APP(3)

经过昨天的介绍後,相信大家对App都有更一步的了解了,是吧!(应该有吧) 那我们今天就来做一点小小的...

【Day16】Git 版本控制 - 多人协作 Fork(1)

透过前面 15 篇的文章,相信大家已经了解要怎麽利用 git 指令将档案进行版本控制、将档案 pus...