iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
自我挑戰組

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

D-17. Rspec 從零開始寫測試(一)

萬事起頭難,有些事常常是不知道怎麼開始。


安裝於Project

$ rails new project_name -T

省略原生Test

$ gem 'rspec-rails', :group => [:development, :test]
$ bundle

產生資源

$ rails generate rspec:install
create  .rspec
create  spec
create  spec/spec_helper.rb
create  spec/rails_helper.rb
#一資料夾兩個檔案,一git版控檔案。

最基礎指令

$ rspec  #目錄底下的測試檔一次跑。
$ rspec spec/models/????_spec.rb #指定跑單一檔。
$ rails generate --help | grep rspec #查可以幫助開哪些測試檔,看了會很後悔?

拿到任何專案,先給他rspec啦!!!


先從model開始吧。

準備工作。

$ rails g model role name job age:integer

create    db/migrate/20210909122636_create_roles.rb
create    app/models/role.rb
invoke    rspec
create    spec/models/role_spec.rb
# migrate、model、spec個一。

$ rails db:create
Created database 'rspec_test_development'
Created database 'rspec_test_test'
$ rails db:migrate
== 20210909122636 CreateRoles: migrating ======================================
-- create_table(:roles)
   -> 0.0084s
== 20210909122636 CreateRoles: migrated (0.0084s) =============================

認識一下空白測試檔。

確認spec/models/role_spec.rb,如未變動任何設定應該長得如下

require 'rails_helper'

RSpec.describe Role, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end

pending讓你回頭測試用,有時有些方法甚至整個想測試的單元都來不及測試,會用pending處理。目前的寫法就是整個Classpending

單一方法需要pending

require 'rails_helper'

RSpec.describe Role, type: :model do
  describe "#some_method" do
    pending
  end
end
#比較常見
RSpec.describe Role, type: :model do
  pending "#some_method"
end

如果是一個方法內還有問題要處理。

RSpec.describe Role, type: :model do
  describe "#some_method" do
    it "計算值不能大於100" do
    end
    pending "常常大於100還沒處理"
  end
end

這是rspec畫面。

.*  //有一個綠燈正確,因為do..end了,只是有待處理事項

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Role#some_method 常常大於100還沒處理
     # Not yet implemented
     # ./spec/models/role_spec.rb:7


Finished in 0.00882 seconds (files took 0.74709 seconds to load)
2 examples, 0 failures, 1 pending

這篇文章我會先把pending先移掉,網路上的教學五花八門,可能每家公司的規範也五花八門雖然我還在家裡宅不是很確定,所以看完本文還請多看文件,至少文件內用法一定正確。


測試驗證(Validations)

先用Validations練手一下。

以下測試單純安裝rspec-rails而已,下篇會介紹其他輔助gem

require 'rails_helper'

RSpec.describe Role, type: :model do
  describe "validates" do
    it "name不能空白"
    it "job不能塊白"
    it "age不能空白"
  end
end

rspec畫面。

***

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Role validates name不能空白
     # Not yet implemented
     # ./spec/models/role_spec.rb:5

  2) Role validates job不能塊白
     # Not yet implemented
     # ./spec/models/role_spec.rb:6

  3) Role validates age不能空白
     # Not yet implemented
     # ./spec/models/role_spec.rb:7


Finished in 0.0017 seconds (files took 0.77157 seconds to load)
3 examples, 0 failures, 3 pending

還是3個*,因為我只有寫it與期望內容,連測試物件都沒有。

修改如下

require 'rails_helper'

RSpec.describe Role, type: :model do

  describe "測試驗證功能" do   #能越清楚越好,英文不好所以我用中文。
    context "所有欄位不能空白" do

      let(:role) {Role.new} #放自己寫的`describe`外即可,此處故意放裡面。

      it "name不可以空白" do
        role.name = nil
        expect(role).to_not be_valid
      end

      it "job不可以空白" do
        role.job = nil
        expect(role).to_not be_valid
      end

      it "age不可以空白" do
        role.age = nil
        expect(role).to_not be_valid
      end
    end
  end
end

一樣先看rspec畫面

FFF

Failures:

  1) Role 測試驗證功能 所有欄位不能空白 name不可以空白
     Failure/Error: expect(role).to_not be_valid
       expected #<Role id: nil, name: nil, job: nil, age: nil, created_at: nil, updated_at: nil> not to be valid
     # ./spec/models/role_spec.rb:12:in `block (4 levels) in <top (required)>

  2) Role 測試驗證功能 所有欄位不能空白 job不可以空白
     Failure/Error: expect(role).to_not be_valid
       expected #<Role id: nil, name: nil, job: nil, age: nil, created_at: nil, updated_at: nil> not to be valid
     # ./spec/models/role_spec.rb:17:in `block (4 levels) in <top (required)>

  3) Role 測試驗證功能 所有欄位不能空白 age不可以空白
     Failure/Error: expect(role).to_not be_valid
       expected #<Role id: nil, name: nil, job: nil, age: nil, created_at: nil, updated_at: nil> not to be valid
     # ./spec/models/role_spec.rb:22:in `block (4 levels) in <top (required)>

Finished in 0.02345 seconds (files took 0.7201 seconds to load)
3 examples, 3 failures

Failed examples:

rspec ./spec/models/role_spec.rb:10 # Role 測試驗證功能 所有欄位不能空白 name不可以空白
rspec ./spec/models/role_spec.rb:15 # Role 測試驗證功能 所有欄位不能空白 job不可以空白
rspec ./spec/models/role_spec.rb:20 # Role 測試驗證功能 所有欄位不能空白 age不可以空白

三個F,因為我還沒在`models/role.rb寫驗證。

describe目前我是拿來描述一個大功能。
context目前我將幾種屬於同狀況的測試包在一起。

letbefore

before寫法

describe "#????" do
  before(:each) do
    @abc = Abc.new( #let與before都可以帶參數 )
  end
end

before(:each) 每個it之前執行,可以只寫 before。
before(:all) 一個RSpec.describe只執行一次。
也有after
after(:each) 每段it之後執行。
after(:all) 一個RSpec.describe後只執行一次。

let

let == before(:each),寫法不同外,let後面的block,會在需使用時才執行,具有lazy loading的效果,提高測試效能,不過以會寫優先,有興趣者可以看看這篇解答:https://stackoverflow.com/questions/5974360/rspec-what-is-the-difference-between-let-and-a-before-block 目前寫短短的,不會有感覺啦XD

toorto_not & be_valid

be_valid可驗證,所以to be_valid可驗證,to_not be_valid不可驗證。
基本語法還是請多看Rspec的使用手冊。


我先加入Rails本身內建驗證器。

app/models/role.rb

class Role < ApplicationRecord
  validates :name, presence: true
  validates :job, presence: true
  validates :age, presence: true
end

rspec畫面

...

Finished in 0.0185 seconds (files took 0.77751 seconds to load)
3 examples, 0 failures

通過了!...明天介紹用Gem輔助。

今日的code。https://github.com/nauosika/Rspec_test/tree/D_17_Rspec_content


今天的leetcode.1512. Number of Good Pairs
題目連結:https://leetcode.com/problems/number-of-good-pairs/
題目重點:A pair (i, j) is called good if nums[i] == nums[j] and i < j.
想辦法紀錄這件事情發生的次數就可以了。

# @param {Integer[]} nums
# @return {Integer}
def num_identical_pairs(nums)

end

puts num_identical_pairs([1,2,3,1,1,3]) #=> 4
puts num_identical_pairs([1,1,1,1]) #=> 6
puts num_identical_pairs([1,2,3]) #=> 0

其實雙迴圈這件事總是會讓我卡住。

2.7.3 :062 > [1, 2, 3].each do |i|
2.7.3 :063 >   [1, 2, 3].each do |j|
2.7.3 :064 >     p [i, j]
2.7.3 :065 >   end
2.7.3 :066 > end
[1, 1]
[1, 2]
[1, 3]
[2, 1]
[2, 2]
[2, 3]
[3, 1]
[3, 2]
[3, 3]
  => [1, 2, 3]

如過還記得雙迴圈那就好辦了。

def num_identical_pairs(nums)
  pairs = 0
  nums.each_with_index do |num1, i|
    nums.each_with_index do |num2, j|
      if num1 == num2 && i < j
        pairs += 1
      end
    end
  end
  pairs
end

不過這題直接用timesfor迴圈比較更簡單明瞭。

def num_identical_pairs(nums)
  pairs = 0
  nums.size.times do |i|
    nums.size.times do |j|
      if nums[i] == nums [j] && i < j
        pairs += 1
      end
    end
  end
  pairs
end

2.7.3 :067 > [1,2,3,1,1,3].size.times do |i|
2.7.3 :068 >   puts i
2.7.3 :069 > end
0
1
2
3
4
5
 => 6

今天...沒有提到面試題。


上一篇
D-18. SQL & NoSQL、SQL injection、primary key & foreign key
下一篇
D-16. Rspec 從零開始寫測試(二) factory_bot_rails && Largest Number At Least Twice of Others
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言