幾乎所有的 Ruby 開發者都聽過 RSpec 然而 Rails 和 Ruby 都是使用名為 Minitest 的測試框架進行測試的,那麼 RSpec 做了什麼事情,讓更多開發者比起使用 Minitest 更喜歡使用 RSpec 呢?
DSL
DSL(Domain Specific Language,領域特定語言)是 Ruby 非常重要的語言特性,因為 Ruby 的類別(Class)本身也是物件,因此我們可以針對類別定義方法,同時允許省略括號等語法的特性,讓 DSL 在 Ruby 變得非常容易實現。
RSpec 就善用了這一點,讓我們能夠以 Ruby 撰寫出極度接近「人類語言」的測試,我們可以看一下這段範例。
1RSpec.describe "User" do
2 subject { User.new(admin: false) }
3
4 it { is_expected.not_to be_admin }
5
6 context 'when user is admin' do
7 subject { User.new(admin: true) }
8
9 it { is_expected.to be_admin }
10 end
11end
閱讀起來基本上不太困難,可以看出來是描述「使用者」同時,一開始的對象是「非管理員使用者」並且,測試的案例為「預期不作為管理員」除此之外還有一個「當使用者是管理員」的情境,用「預期作為使用者」的測試案例。
假設我們要使用 Minitest 來撰寫這個測試,則會變成像這樣
1require "minitest/autorun"
2
3class TestUser < Minitest::Test
4 def test_user_not_be_admin
5 @user = User.new(admin: false)
6 assert_equal false, @user.admin?
7 end
8
9 def test_user_be_a_admin
10 @user = User.new(admin: true)
11 assert_equal false, @user.admin?
12 end
13end
看起來似乎就沒那麼直覺,雖然有一部分原因是是因為對 Minitest 不太熟悉,然而這樣的撰寫方式仍讓人不是那麼容易學習。
現在 Minitest 有支援 Specs 的寫法,更接近 RSpec 的的寫法。
Behavior-Driven Development
除了 DSL 的特性外,RSpec 本身也是一個「行為驅動開發」的測試框架。我們在開發軟體的過程中,會很習慣的以「物件」或者「方法」的角度去思考,然而在 RSpec 中我們更關注的是對「行為」進行測試。
類似於上一個章節提到的 A-TDD(驗收測試區動開發)的方式,我們希望在實作之前先撰寫測試,如果 A-TDD 是針對「功能」來撰寫,那麼 BDD 則是針對「行為」來撰寫。
功能和行為的差異在哪裡呢?簡單來說「功能」是使用者一系列操作的組合,而行為則是某個程式邏輯,也就是說非常適合用來撰寫「單元測試」類型的測試案例。
我在 LeSS in Action 課程中學到的一個有趣觀念,單元測試跟端對端測試(E2E Testing)的差異,主要是「範圍」的大小,而 BDD 大多剛好是一個「單元」大小的測試。
舉例來說,我們想要實作一個「訂閱」的動作,就可以像這樣撰寫一個測試
1RSpec.describe User do
2 # ...
3
4 describe '#subscribe!' do
5 subject(:subscribe) { user.subscribe! }
6
7 it { is_expected.to be_truthy }
8
9 context 'when use is subscribed' do
10 before { subscribe }
11
12 it { expect { subscribe }.to raise_error(User::AlreadySubscribed) }
13 end
14 end
15end
我們可以描述一個 #subscribe!
方法定為這個測試的主題,如果一切正常的話則回傳 true
除此之外,有一個「已經訂閱」的情境,我們先在測試前執行一次「訂閱」當我們再次驗證「訂閱」動作時,就會預期拋出 User::AlreadySubscribed
的例外,撰寫測試就變得相當輕鬆。
我認為這樣直覺的撰寫方式,以及完善的生態系,是讓 RSpec 深受大部分 Ruby 開發者喜愛的原因。
如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用優雅的 RSpec 測試回饋表單告訴我。
如果對這篇文章有興趣,可以透過以下連結繼續閱讀這系列的其他文章。
- 優雅的 RSpec 測試 - 前言
- 優雅的 RSpec 測試 - 撰寫測試的方式
- 優雅的 RSpec 測試 - RSpec 概觀
- 優雅的 RSpec 測試 - 測試案例
- 優雅的 RSpec 測試 - 組織測試
- 優雅的 RSpec 測試 - 前置處理
- 優雅的 RSpec 測試 - 常見匹配器
- 優雅的 RSpec 測試 - 內容匹配
- 優雅的 RSpec 測試 - 錯誤匹配
- 優雅的 RSpec 測試 - 共用案例
- 優雅的 RSpec 測試 - 自訂匹配器
- 優雅的 RSpec 測試 - 輔助方法
- 優雅的 RSpec 測試 - 測試替身
- 優雅的 RSpec 測試 - Mock 與 Stub
- 優雅的 RSpec 測試 - Allow 的使用方式
- 優雅的 RSpec 測試 - Allow 變化應用
- 優雅的 RSpec 測試 - Spy 的應用
- 優雅的 RSpec 測試 - 物件的可測試性
- 優雅的 RSpec 測試 - 耦合與依賴注入
- 優雅的 RSpec 測試 - 探索式的測試與重構