Website logo

Robert Chang

技術部落格

RSpec - 學會使用 subject

還記得我們使用 let 方法來實作一個物件讓我們可以快速地使用!

但有沒有什麼好方法可以讓我不要想物件的名字?可能因為我有取名的障礙或是英文真的太爛了!

甚至我連 let 都懶得寫,實在是太懶惰了…

有! RSpec 有個有趣的方法叫做 subject,他會自動根據整個 RSpec 最上層的類別名稱來決定 subject 該是什麼!

Subject

剛剛的說法也都比不過一個實際的例子來得實際,看看以下範例!

RSpec.describe Hash do
  it "should be empty" do
    expect(subject.length).to eq(0)
  end
end

這是 output 的結果:

screen shot

現在一定是一頭霧水,想說這個 subject 到底是在哪裡?我們甚至沒有宣告它啊?

這就是 RSpec 提供的便利方法,如果最上層 RSpec.describe 後方接的是類別,那 subject 就會依照這個類別的預設值去做 new 的動作。

我們可以把 subject 給印出來看看!

RSpec.describe Hash do
  it "should be empty" do
    p subject
    p subject.class
    expect(subject.length).to eq(0)
  end
end

output 的結果:

screen shot

所以我們可以想像,它在上方幫我們做了:

let(:subject) { Hash.new }

這是動態產生的,根據我們提供的類別來建立物件!是不是很方便呢?

加上他和 let 一樣,在每一個 example 中都是獨立的,不會有污染物件的情形,可以放心地使用!

加入初始值的 Subject

剛剛示範的是完全乾淨的一個物件,但如果我們想要建立一個有不一樣初始值的 subject 該怎麼做呢?

馬上來示範一下:

RSpec.describe Hash do
  subject do
    {a: 3, b: 4}
  end

  it "should be empty" do
    expect(subject.length).to eq(2)
  end
end

其實很簡單,但如果不想要叫做 subject 呢?

只要在後面加入變數的命名就可以了!

RSpec.describe Hash do
  subject(:robert) do
    {a: 3, b: 4}
  end

  it "should be empty" do
    expect(subject.length).to eq(2)
    expect(robert.length).to eq(2)
  end
end

至於為什麼要這樣用?使用 let 還不是一樣,何必浪費時間寫三行呢?

你說的沒錯,用 let 的效果也是一樣,但 subject 有其他更魔法的方法可以使用,等等也會一併介紹,會讓寫出來的 Code 優雅到不行!

在介紹黑魔法之前,先來介紹另一個也很有趣的語法。

Described_class

剛剛介紹的是 subejct 的好用之處,這次介紹的 described_class 可能不一定會用到,但在改大量程式碼的時候很有用!

先用範例來解說一下:

class Burger
  attr_accessor :beef, :cheese

  def initialize(beef, cheese)
    @beef = beef
    @cheese = cheese
  end
end

RSpec.describe Burger do
  subject { Burger.new("Beef", "Cheedar") }
  let(:pork_burger) { Burger.new("Pork", "Cheedar") }

  it "...." do
    ....
  end
end

這邊有個漢堡的類別,假設需求今天下來是要更改這個 Burger 類別的名稱,怎麼辦?

任務是把 Burger 改成 GoodBurger 這個名字,若是只有兩三行,那倒是沒什麼問題,但如果是 100 行呢? 要怎麼做?

這時候 described_class 就可以派上用場,若是一開始都用這樣的寫法就好了:

class GoodBurger
  attr_accessor :beef, :cheese

  def initialize(beef, cheese)
    @beef = beef
    @cheese = cheese
  end
end

# 我們只需要更改這邊的類別名稱就可以了!
RSpec.describe GoodBurger do
  subject { described_class.new("Beef", "Cheedar") }
  let(:pork_burger) { described_class.new("Pork", "Cheedar") }

  it "...." do
    ....
  end
end

described_class 雖然不是一個很常使用的東西,但它就是可以根據你上面放置的類別來動態產生物件,而且只需要改類別名稱就能夠一次跟上!

說不定在某些時候會幫助到你!

我只要寫一行

前面有提到使用 subject 的小小好處,就是可以讓你只要寫一行!

直接先看程式碼:

RSpec.describe 5 do
  it "should be 5" do
    expect(subject).to eq(5)
  end

  context "one line syntax" do
    it { is_expected.to eq(5) }
  end
end

這是 output

screen shot

第三行是很常使用的寫法,所以沒有什麼問題。

但第七行就是 subject 的神奇之處, is_expected 這個方法本身的主詞就是 subject,所以可以這樣子就寫完範例。

不覺得看起來整個就超級 Ruby 的嗎?,該省的都要給我省下來,越短越好!

希望這可以讓你在寫某些簡單的 example 中可以派上用場!

Don’t Repeat Yourself 能不要重複撰寫就盡量節省!

結語

今天介紹了 subject & described_class 還有一行流的寫法!

不一定是常常會使用到,但都是 RSpec 很基礎的語法,至少先記在腦袋裡面,想起來再去查資料就可以了!

現在介紹的都是整理單個測試的程式碼,但如果在不同的測試有著相同的 example 呢?

上一篇文章RSpec - Hooks 們以及作用域的差別

下一篇文章RSpec - 分享範例,整理範例