RSpec 3 的重大變更
Myron Marston
2014年5月21日更新:現在有提供此文章的日文翻譯。
RSpec 3.0.0 RC1 已在幾天前發布,而 3.0.0 的最終版本也即將推出。我們已經使用了 beta 版 6 個月了,並且很興奮能與您分享它們。以下是新功能:
所有 gem 的變更
移除對 Ruby 1.8.6 和 1.9.1 的支援
這些 Ruby 版本早已終止生命週期,RSpec 3 不支援它們。
改進對 Ruby 2.x 的支援
最近發布的 RSpec 2.x 版本(即在 Ruby 2.0 發布之後的版本)已正式支援 Ruby 2,但 RSpec 3 的支援已大幅改進。我們現在支援使用 Ruby 2 的新功能,例如關鍵字引數和前置模組。
新的 rspec-support gem
rspec-support 是一個新的 gem,我們將它用於 rspec-(core|expectations|mocks|rails) 中多個 gem 所需的通用程式碼。它目前不包含任何供終端使用者或擴充函式庫作者使用的公開 API,但我們未來可能會公開它的一些 API。
如果您透過在 Gemfile 中從 github 取得來源來執行最前沿的 RSpec,您也需要開始對 rspec-support 執行相同的操作。
穩健且經過良好測試的升級流程
RSpec 3 中的每個重大變更在 2.99 版中都有對應的棄用警告。在整個 beta 版中,我們進行了許多升級,以確保此流程盡可能順利。我們整理了逐步升級說明。
升級流程還強調了 RSpec 新的棄用系統,該系統具有高度可配置性(允許您將棄用輸出到檔案或將所有棄用轉換為錯誤),並且旨在最大限度地減少重複的棄用輸出。
改進的文件
我們投入大量精力來更新所有 gem 的 API 文件。它們目前託管在 rubydoc.info 上。
…但我們目前正在更新 rspec.info 來自行託管它們。
雖然文件仍在進行中(坦白說,永遠都會如此),但我們已確保將所有公開 API 明確聲明為 SemVer 合規的一部分。我們絕對致力於在所有 3.x 版本中維護所有公開 API。另一方面,私有 API 被標記為私有,因為我們特別希望保留在任何 3.x 版本中隨意變更它們的彈性。
請勿使用我們宣告為私有的 API。如果您發現現有的公開 API 無法滿足您的需求,請提出詢問。我們很樂意為了您的需求而將私有 API 公開,或新增一個新的 API 來滿足您的使用案例。
gem 現在已簽署
我們已開始簽署我們的 gem 發行版本。雖然目前的 gem 簽署系統遠非理想,並且正在開發更好的解決方案,但總比沒有好。我們已將我們的公開憑證放在 GitHub 上。
如需目前 gem 簽署系統的更多詳細資訊,請參閱使用簽署 Ruby Gem 的實用指南。
零猴子修補模式
現在可以使用 RSpec,而無需任何猴子修補。這項工作的大部分基礎是在最近的 2.x 版本中奠定的,這些版本將新的 expect
型語法新增到 rspec-expectations 和 rspec-mocks 中。我們在 RSpec 3 中完成了其餘工作,並為其餘的猴子修補提供了替代方案。
為了方便起見,您可以使用一個選項停用所有猴子修補
# spec/spec_helper.rb
RSpec.configure do |c|
c.disable_monkey_patching!
end
感謝 Alexey Fedorov 實作此設定選項。
更多資訊
rspec-core
hook 範圍的新名稱::example
和 :context
RSpec 2.x 有三個不同的 hook 範圍
describe MyClass do
before(:each) { } # runs before each example in this group
before(:all) { } # runs once before the first example in this group
end
# spec/spec_helper.rb
RSpec.configure do |c|
c.before(:each) { } # runs before each example in the entire test suite
c.before(:all) { } # runs before the first example of each top-level group
c.before(:suite) { } # runs once after all spec files have been loaded, before the first spec runs
end
有時,使用者對 :each
與 :all
的含義表示困惑,特別是當您在設定區塊中使用 :all
時,可能會感到困惑
# spec/spec_helper.rb
RSpec.configure do |c|
c.before(:all) { }
end
在此內容中,術語 :all
表示此 hook 將在套件中的所有範例之前執行一次 — 但這才是 :suite
的用途。
在 RSpec 3 中,:each
和 :all
有別名,使其範圍更加明確::example
是 :each
的別名,而 :context
是 :all
的別名。請注意,:each
和 :all
並「不」棄用,我們也無意這麼做。
感謝 John Feminella 實作此功能。
更多資訊
DSL 方法將範例作為引數傳遞
RSpec::Core::Example
提供對範例所有詳細資訊的存取權:其描述、位置、中繼資料、執行結果等。在 RSpec 2.x 中,範例是透過 example
方法公開的,該方法可以從任何 hook 或個別範例存取
describe MyClass do
before(:each) { puts example.metadata }
end
在 RSpec 3 中,我們已移除 example
方法。相反地,範例執行個體會以明確引數的形式傳遞至所有範例範圍的 DSL 方法
describe MyClass do
before(:example) { |ex| puts ex.metadata }
let(:example_description) { |ex| ex.description }
it 'accesses the example' do |ex|
# use ex
end
end
感謝 David Chelimsky 提出這個想法,並感謝他實作此功能!
更多資訊
新的 expose_dsl_globally
設定選項可停用 rspec-core 猴子修補
RSpec 2.x 猴子修補了 main
和 Module
,以提供頂層方法,例如 describe
、shared_examples_for
和 shared_context
shared_examples_for "something" do
end
module MyGem
describe SomeClass do
it_behaves_like "something"
end
end
在 RSpec 3 中,這些方法現在也可以在 RSpec
模組上使用(除了仍然可以作為猴子修補使用)
RSpec.shared_examples_for "something" do
end
module MyGem
RSpec.describe SomeClass do
it_behaves_like "something"
end
end
您可以將新的 expose_dsl_globally
設定選項設定為 false
,以完全移除 rspec-core 的猴子修補(這會使上面的第一個範例引發 NoMethodError
)
# spec/spec_helper.rb
RSpec.configure do |config|
config.expose_dsl_globally = false
end
更多資訊
使用 alias_example_group_to
定義範例群組別名
在 RSpec 2.x 中,我們提供了一個 API,允許您定義具有附加中繼資料的 example
別名。例如,這在內部用於將 fit
定義為 it
的別名,其中包含 :focus => true
中繼資料
# spec/spec_helper.rb
RSpec.configure do |config|
config.alias_example_to :fit, :focus => true
end
在 RSpec 3 中,我們已將此功能擴充到範例群組
# spec/spec_helper.rb
RSpec.configure do |config|
config.alias_example_group_to :describe_model, :type => :model
end
您可以在使用 rspec-rails 的專案中使用此範例,並使用 describe_model User
而不是 describe User, :type => :model
。
感謝 Michi Huber 實作此功能。
更多資訊
新的範例群組別名:xdescribe
、xcontext
、fdescribe
、fcontext
除了包含一個用於定義範例群組別名的 API 之外,我們還包含了一些額外的內建別名(在 describe
和 context
之上)
- 與範例的
xit
類似,可以使用xdescribe
/xcontext
來暫時略過範例群組。 - 與範例的
fit
類似,可以使用fdescribe
/fcontext
將:focus => true
中繼資料暫時新增至範例群組,以便您可以透過config.filter_run :focus
輕鬆篩選到聚焦的範例和群組。
更多資訊
對 pending
語意的變更(並引入 skip
)
現在會執行擱置範例,以檢查它們是否實際通過。如果擱置區塊失敗,則會像之前一樣標示為擱置。但是,如果它成功,則會導致失敗。這有助於確保擱置的範例有效,並在實作其描述的行為時立即處理它們。
為了支援舊的「永不執行」行為,已新增 skip
方法和中繼資料。永遠不會執行以下任何範例
describe Post do
skip 'not implemented yet' do
end
it 'does something', :skip => true do
end
it 'does something', :skip => 'reason explanation' do
end
it 'does something else' do
skip
end
it 'does something else' do
skip 'reason explanation'
end
end
有了此變更,將區塊傳遞至範例中的 pending
不再有意義,因此已移除該行為。
感謝 Xavier Shay 實作此功能。
更多資訊
單行程式碼的新 API:is_expected
RSpec 已有多年的單行語法
describe Post do
it { should allow_mass_assignment_of(:title) }
end
在此內容中,should
並「不是」猴子修補的 should
,您可以透過將 rspec-expectations 設定為僅支援 :expect
語法來移除該語法。它沒有猴子修補 Object
與 should
所帶來的負擔,並且無論您的語法設定如何,始終可用。
一些使用者對此 should
如何與 expect
語法相關以及是否可以繼續使用它表示困惑。它將繼續在 RSpec 3 中提供(再次,無論您的語法設定如何),但我們也新增了一個替代 API,它與 expect
語法更一致
describe Post do
it { is_expected.to allow_mass_assignment_of(:title) }
end
is_expected
非常簡單地定義為 expect(subject)
,並且也透過 is_expected.not_to matcher
支援負面預期。
更多資訊
可以個別排序範例群組
RSpec 2.8 為 RSpec 引入了隨機排序,這對於顯示您的規格套件中無意的排序相依性非常有用。在 RSpec 3 中,它不再是全有或全無的功能。您可以透過使用適當的中繼資料標記範例群組來控制它們的排序方式
describe MyClass, :order => :defined do
# examples in this group will always run in defined order,
# regardless of any other ordering configuration.
end
describe MyClass, :order => :random do
# examples in this group will always run in random order,
# regardless of any other ordering configuration.
end
這對於從已定義的排序移轉至隨機排序特別有用,因為它允許您在為特定群組選擇加入該功能時逐一處理排序相依性,而不是必須一次解決所有問題。
作為此的一部分,我們還將 --order default
重新命名為 --order defined
,因為我們意識到「default」是一個過於重載的詞彙。
感謝 Andy Lindeman 和 Sam Phippen 協助實作此功能。
更多資訊
新的排序策略 API
在 RSpec 3 中,我們徹底修改了排序策略 API。過去的 三個 不同的 方法 現在整合為單一方法:register_ordering
。您可以使用它來定義具名的排序策略
# spec/spec_helper.rb
RSpec.configure do |config|
config.register_ordering(:description_length) do |list|
list.sort_by { |item| item.description.length }
end
end
describe MyClass, :order => :description_length do
# ...
end
或者,您可以使用它來定義全域排序
# spec/spec_helper.rb
RSpec.configure do |config|
config.register_ordering(:global) do |list|
# sort them alphabetically
list.sort_by { |item| item.description }
end
end
:global
排序用於排序最上層的範例群組,以及所有沒有 :order
元資料的範例群組。
更多資訊
rspec --init
的改進
rspec
命令長期以來都提供 --init
選項,以建立專案的骨架。在 RSpec 3 中,它產生的檔案已大幅改進,以提供更好的開箱即用體驗,並提供包含更多建議設定的 spec/spec_helper.rb
檔案。
請注意,未來的預設設定中不會包含的建議設定,會在產生的檔案中註解掉,因此開啟檔案並接受您想要的建議是個好主意。
更多資訊
新的 --dry-run
CLI 選項
此選項將印出您的測試套件的格式器輸出,而不會執行任何範例或 hook。它特別適用於檢閱您的套件文件輸出,而無需等待規格執行或擔心它們的通過/失敗狀態。
感謝 Thomas Stratmann 貢獻 這個功能!
更多資訊
格式器 API 的變更
已新增一個全新的格式器 API,更具彈性。
- 僅訂閱您關心的事件。
- 方法接收通知物件,而不是特定的參數,因此可以在向後相容的方式下新增新的通知資料。
- 通知物件上公開了 helper 方法,因此不再需要繼承
BaseTextFormatter
。
新的格式器看起來像這樣
class CustomFormatter
RSpec::Core::Formatters.register self, :example_started
def initialize(output)
@output = output
end
def example_started(notification)
@output << "example: " << notification.example.description
end
end
為了繼續支援舊的 2.x 格式器 API,我們提供了 rspec-legacy_formatters gem。
感謝 Jon Rowe 負責處理這件事。
更多資訊
斷言設定變更
雖然大多數使用者使用 rspec-expectations,但使用其他工具也很簡單,RSpec 2.x 透過設定選項,讓最常見的替代方案更容易取得
# spec/spec_helper.rb
RSpec.configure do |config|
config.expect_with :stdlib
# or, to use both:
config.expect_with :stdlib, :rspec
end
然而,對於 :stdlib
一直存在困惑。在 Ruby 1.8 上,標準函式庫斷言模組是 Test::Unit::Assertions
。在 1.9+ 上,它是 Minitest::Assertions
的薄包裝器(而且您通常最好直接使用它)。同時,還有一個定義 Test::Unit::Assertions
的 test-unit gem(它*不是* minitest 的包裝器)和一個 minitest gem。
對於 RSpec 3,我們已移除 expect_with :stdlib
,改為明確的 :test_unit
和 :minitest
選項
# spec/spec_helper.rb
RSpec.configure do |config|
# for test-unit:
config.expect_with :test_unit
# for minitest:
config.expect_with :minitest
end
感謝 Aaron Kromer 實作了這個功能。
更多資訊
定義衍生元資料
RSpec 的元資料系統非常靈活,允許您以多種方式切割和剖析您的測試套件。有一個新的設定 API 允許您定義衍生的元資料。例如,自動將 spec/acceptance/js
中的所有範例群組標記為 :js => true
# spec/spec_helper.rb
RSpec.configure do |config|
config.define_derived_metadata(:file_path => %r{/spec/acceptance/js/}) do |metadata|
metadata[:js] = true
end
end
更多資訊
移除
一些不再是 RSpec 核心的功能,已被完全移除或提取到外部 gem 中
- Textmate 格式器已移至 Textmate bundle 中。在 rspec-core 中為一個特定的文字編輯器提供格式器並不是真的有意義。
- 已刪除 RCov 整合。它從未更新為與 1.9+ 一起使用,現在我們建議改用 simplecov。
- 已移除
--debug
CLI 選項。現在有很多不同的除錯器選項,您可以使用--require
(或-r
) 選項從命令列啟用它們。例如,若要使用 byebug,請在命令列傳遞-rbyebug
。 - 我們已移除
--line-number
CLI 選項。它的語意從一開始就很可疑(--line-number 43
會篩選到每個已載入規格檔案中第 43 行附近定義的範例,但沒有理由每個檔案中的第 43 行都會相關),並且重複使用更簡潔的path/to/spec.rb:43
形式。 its
已提取到新的 rspec-its gem 中,Peter Alfvin 很樂意維護它。- Autotest 整合已提取到新的 rspec-autotest gem 中(可能需要維護者:有志願者嗎?)。
rspec-expectations
使用 should
語法而不明確啟用它已過時
在 RSpec 2.11 中,我們開始透過引入新的基於 expect 的語法,朝著消除 RSpec 的 monkey patching 方向邁進。在 RSpec 3 中,我們保留了 should
語法,並且預設可用,但如果您使用它而不明確啟用它,將會收到棄用警告。這將為它在 RSpec 4 中預設停用(或可能提取到單獨的 gem 中)鋪路,同時最大限度地減少新手透過舊的教學課程接觸 RSpec 時的困惑。
我們認為 expect
語法現在是 RSpec 的「主要」語法,但如果您偏好較舊的基於 should
的語法,可以繼續使用它:我們沒有計畫要永遠殺死它。
感謝 Sam Phippen 實作了這個功能。
更多資訊
複合匹配器表達式
在 RSpec 3 中,您可以使用 and
或 or
將多個匹配器串聯在一起
# these two expectations...
expect(alphabet).to start_with("a")
expect(alphabet).to end_with("z")
# ...can be combined into one expression:
expect(alphabet).to start_with("a").and end_with("z")
# You can also use `or`:
expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
它們被別名為 &
和 |
運算符
expect(alphabet).to start_with("a") & end_with("z")
expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
感謝 Eloy Espinaco 建議並實作了這個功能,並感謝 Adam Farhi 使用 &
和 |
運算符擴展了它。
更多資訊
可組合的匹配器
RSpec 3 允許您透過將匹配器作為參數傳遞給其他匹配器來表達詳細的意圖
s = "food"
expect { s = "barn" }.to change { s }.
from( a_string_matching(/foo/) ).
to( a_string_matching(/bar/) )
expect { |probe|
"food".tap(&probe)
}.to yield_with_args( a_string_starting_with("f") )
為了提高程式碼表達式和失敗訊息的可讀性,大多數匹配器都有別名,在這些類型的表達式中作為參數傳遞時可以正確讀取。
更多資訊
- RSpec 3 的新功能:可組合的匹配器
- rspec-expectations #280 - 原始討論
- rspec-expectations #393 - 實作
- API 文件(包括匹配器別名列表)
- Relish 文件
match
匹配器可以用於資料結構
在 RSpec 3 之前,match
匹配器使用 #match
方法來執行字串/regex 匹配
expect("food").to match("foo")
expect("food").to match(/foo/)
在 RSpec 3 中,它還支援匹配任意巢狀的陣列/雜湊資料結構。預期的值可以使用任何巢狀層級的匹配器來表示
hash = {
:a => {
:b => ["foo", 5],
:c => { :d => 2.05 }
}
}
expect(hash).to match(
:a => {
:b => a_collection_containing_exactly(
an_instance_of(Fixnum),
a_string_starting_with("f")
),
:c => { :d => (a_value < 3) }
}
)
更多資訊
新的 all
匹配器
此匹配器可讓您指定集合中所有項目都為 true。將匹配器作為參數傳遞
expect([1, 3, 5]).to all( be_odd )
感謝 Adam Farhi 貢獻了這個功能!
更多資訊
新的 output
匹配器
此匹配器可用於指定程式碼區塊寫入 stdout 或 stderr
expect { print "foo" }.to output("foo").to_stdout
expect { print "foo" }.to output(/fo/).to_stdout
expect { warn "bar" }.to output(/bar/).to_stderr
感謝 Matthias Günther 提出建議(並讓事情開始運作),並感謝 Luca Pette 將這個功能完成。
更多資訊
新的 be_between
匹配器
RSpec 2 使用動態述詞支援為實作 between?
的物件提供了一個 be_between
匹配器。在 RSpec 3 中,我們獲得了一個更好的第一級 be_between
匹配器,它在幾個方面都更好
- 失敗訊息更好得多 — 它不會告訴您
between?(1, 10)
返回 false,而是會告訴您expected 11 to be between 1 and 10
。 - 它適用於實作比較運算符(例如
<
、<=
、>
、>=
),但不實作between?
的物件。 - 它提供
inclusive
和exclusive
模式。
# like `Comparable#between?`, it is inclusive by default
expect(10).to be_between(5, 10)
# ...but you can make it exclusive:
expect(10).not_to be_between(5, 10).exclusive
# ...or explicitly label it inclusive:
expect(10).to be_between(5, 10).inclusive
感謝 Erik Michaels-Ober 貢獻了這個功能,並感謝 Pedro Gimenez 改進了它!
更多資訊
布林匹配器已重新命名
RSpec 2 有一對匹配器(be_true
和 be_false
)反映了 Ruby 的條件語意:be_true
對於 nil
或 false
以外的任何值都會通過,而 be_false
對於 nil
或 false
都會通過。
在 RSpec 3 中,我們將這些重新命名為 be_truthy
和 be_falsey
(或 be_falsy
,如果您喜歡這個拼寫),使其語意更明確,並減少與 be true
/be false
的混淆(它們讀起來與 be_true
/be_false
相同,但僅在給出確切的 true
/false
值時才通過)。
感謝 Sam Phippen 實作了這個功能。
更多資訊
match_array
匹配器現在可作為 contain_exactly
使用
RSpec 長期以來都有一種匹配器,可讓您匹配兩個陣列的內容,同時忽略任何排序差異。最初,這可以使用舊的 should
語法的 =~
運算符來使用
[2, 1, 3].should =~ [1, 2, 3]
後來,當我們新增 expect
語法時,我們決定不將運算符匹配器帶到新的語法中,並將匹配器命名為 match_array
expect([2, 1, 3]).to match_array([1, 2, 3])
match_array
是我們當時能想到的最好名稱,但我們對此並不是很滿意:「match」是一個不精確的術語,而且匹配器旨在處理陣列以外的其他種類的集合。我們在 RSpec 3 中想出了更好的名稱
expect([2, 1, 3]).to contain_exactly(1, 2, 3)
請注意,match_array
*並未* 被棄用。這兩個方法的行為相同,只是 contain_exactly
會將項目個別展開傳遞,而 match_array
則會接受單一陣列參數。
更多資訊
集合基數匹配器提取到 rspec-collection_matchers
gem 中
集合基數匹配器 — have(x).items
、have_at_least(y).items
和 have_at_most(z).items
— 是 RSpec 中比較「神奇」且令人困惑的部分之一。它們已提取到 rspec-collection-matchers gem 中,Hugo Baraúna 很樂意自願維護它。
一般替代方案是對集合的大小設定期望
expect(list).to have(3).items
# ...can be written as:
expect(list.size).to eq(3)
expect(list).to have_at_least(3).items
# ...can be written as:
expect(list.size).to be >= 3
expect(list).to have_at_most(3).items
# ...can be written as:
expect(list.size).to be <= 3
改進與 Minitest 的整合
在 RSpec 2.x 中,rspec-expectations 會自動將其自身包含在 MiniTest::Unit::TestCase
或 Test::Unit::TestCase
中,如此一來您只需載入它,便能在 Minitest 或 Test::Unit 中使用 rspec-expectations。
在 RSpec 3 中,我們以幾種方式更新了這個整合。
- 與 Minitest 4(或更低版本)或 Test::Unit 的整合不再是自動的。如果您在這樣的環境中使用 rspec-expectations,您需要自己
include RSpec::Matchers
。 - 現在提供了與 Minitest 5 更好的整合,但您必須透過
require 'rspec/expectations/minitest_integration'
明確地載入它。
更多資訊
Matcher 協定的變更
如上所述,在 RSpec 3 中,我們不再認為 should
是 rspec-expectations 的主要語法。我們已更新 matcher 協定以反映這一點。
failure_message_for_should
現在是failure_message
。failure_message_for_should_not
現在是failure_message_when_negated
。match_for_should
(自訂 matcher DSL 中的match
的別名)已被移除,沒有替代方案。(直接使用match
)。- 自訂 matcher DSL 中的
match_for_should_not
現在是match_when_negated
。
此外,我們還新增了 supports_block_expectations?
作為 matcher 協定中新的可選部分。這用於在使用者錯誤地在區塊期望表達式中使用值 matcher 時,給予使用者清楚的錯誤訊息。例如,在進行此變更之前,當使用像 be_nil
這樣的 matcher 時,將區塊傳遞給 expect
可能會導致誤判。
expect { foo.bar }.not_to be_nil
# ...is equivalent to:
block = lambda { foo.bar }
expect(block).not_to be_nil
# ...but the block is not nil (even though `foo.bar` might return nil),
# so the expectation will pass even though the user probably meant:
expect(foo.bar).not_to be_nil
請注意,supports_block_expectations?
是協定中可選的一部分。對於不打算在區塊期望表達式中使用的 matcher,您不需要定義它。
更多資訊
- rspec-expectations #270 - 原始討論
- rspec-expectations #373 - 實作
- rspec-expectations #530 -
supports_block_expectations?
的原始討論 - rspec-expectations #530 -
supports_block_expectations?
的實作
rspec-mocks
在未明確啟用它的情況下使用 monkey-patched 語法已被棄用。
與 rspec-expectations 一樣,我們也一直在將 rspec-mocks 朝向零 monkey patching 語法的方向發展。這在 2.14 版中最初引入。在 RSpec 3 中,如果您在未明確啟用的情況下使用原始語法(例如 obj.stub
、obj.should_receive
等),您將收到棄用警告(就像 rspec-expectations 的新語法一樣)。
感謝 Sam Phippen 實作 此功能。
新語法的 receive_messages
和 receive_message_chain
最初的 monkey patching 語法有一些在 2.14 版發布的新語法中缺乏的功能。我們在 RSpec 3 中透過幾個新的 API 解決了這個問題:receive_messages
和 receive_message_chain
。
# old syntax:
object.stub(:foo => 1, :bar => 2)
# new syntax:
allow(object).to receive_messages(:foo => 1, :bar => 2)
# old syntax:
object.stub_chain(:foo, :bar, :bazz).and_return(3)
# new syntax:
allow(object).to receive_message_chain(:foo, :bar, :bazz).and_return(3)
這些新 API 的一個好處是它們也可以與 expect
一起使用,而在舊語法中,沒有與 stub(hash)
或 stub_chain
對等的訊息期望。
感謝 Jon Rowe 和 Sam Phippen 實作此功能。
更多資訊
receive_messages
的文件receive_message_chain
的文件- rspec-mocks #368 - 關於
receive_messages
的討論 - rspec-mocks #399 -
receive_messages
的實作 - rspec-mocks #464 - 關於
receive_message_chain
的討論 - rspec-mocks #467 -
receive_message_chain
的實作
已移除 mock
和 stub
作為 double
的別名
過去,rspec-mocks 提供了 3 個方法來建立測試替身:mock
、stub
和 double
。在 RSpec 3 中,我們已移除 mock
和 stub
,只保留 double
,並建立了更多使用 double
命名法的功能(例如驗證替身,請參閱下文)。
當然,雖然 RSpec 3 不再提供 mock
和 stub
作為 double
的別名,但如果您想繼續使用它們,您可以輕鬆地自行定義這些別名。
# spec/spec_helper.rb
module DoubleAliases
def mock(*args, &block)
double(*args, &block)
end
alias stub mock
end
RSpec.configure do |config|
config.include DoubleAliases
end
感謝 Sam Phippen 實作 此功能。
更多資訊
驗證替身
新增了一種新的替身類型,它確保您只 stub 或 mock 實際存在的方法,並且傳遞的參數符合宣告的方法簽名。如果未滿足這些條件,則 instance_double
、class_double
和 object_double
替身都會引發例外。如果尚未載入類別(通常是在隔離執行單元測試時),則不會引發任何例外。
這是一個細微的行為,但非常強大,因為它允許隔離單元測試的速度,並具有接近整合測試(或類型系統)的信心。很少有理由不使用這些新的、更強大的替身類型。
感謝 Xavier Shay 提供這個功能的想法和實作。
更多資訊
部分替身驗證設定選項
也可以在部分替身上全域啟用驗證替身行為。(部分替身是指當您 mock 或 stub 現有物件時:expect(MyClass).to receive(:some_message)
。)
# spec/spec_helper.rb
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
我們建議您為所有新程式碼啟用此選項。
範圍變更
rspec-mocks 的操作是根據每個測試的生命週期設計的。這已在 RSpec 2 中記錄,但並非總是在執行時明確強制執行,當使用者嘗試在每個測試生命週期之外使用 rspec-mocks 的功能時,我們有時會收到錯誤報告。
在 RSpec 3 中,我們已經加強了這一點,並且這個生命週期在執行時會明確強制執行。
- 不支援在
before(:context)
hook(或在沒有目前範例的任何其他上下文中)使用 rspec-mocks 的功能。 - 測試替身僅可用於一個範例。如果您嘗試在測試替身來源的範例之外使用它(例如,透過意外將它分配給類別屬性,然後在稍後的範例中使用它),您將收到明確的錯誤訊息。
我們還提供了一個新的 API,讓您可以在任意位置(例如 before(:context)
hook)建立臨時範圍。
describe MyWebCrawler do
before(:context) do
RSpec::Mocks.with_temporary_scope do
allow(MyWebCrawler).to receive(:crawl_depth_limit).and_return(5)
@crawl_results = MyWebCrawler.perform_crawl_on("http://some-host.com/")
end # verification and resets happen when the block completes
end
# ...
end
感謝 Sam Phippen 協助 實作 這些變更,以及 Sebastian Skałacki 建議新的 with_temporary_scope
功能。
更多資訊
any_instance
實作區塊會產生接收器
當為方法 stub 提供實作區塊時,根據物件的狀態進行一些計算可能很有用。不幸的是,在 RSpec 2 中使用 any_instance
時,沒有簡單的方法可以做到這一點。在 RSpec 3 中,接收器會作為第一個參數產生到 any_instance
實作區塊,使其變得容易。
allow_any_instance_of(Employee).to receive(:salary) do |employee, currency|
usd_amount = 50_000 + (10_000 * employee.years_worked)
currency.from_usd(usd_amount)
end
employee = Employee.find(23)
salary = employee.salary(Currency.find(:CAD))
感謝 Sam Phippen 實作 此功能。
更多資訊
rspec-rails
預設情況下停用檔案類型推斷
rspec-rails 會根據規格在檔案系統上的位置自動新增元數據。這會讓新使用者感到困惑,並且對某些資深使用者來說並不理想。
在 RSpec 3 中,必須明確啟用此行為。
# spec/spec_helper.rb
RSpec.configure do |config|
config.infer_spec_type_from_file_location!
end
由於這種假設的行為在教學中非常普遍,因此預設產生的設定仍然會啟用此功能。
若要明確標記規格而不使用自動推斷,請設定 type
元數據。
RSpec.describe ThingsController, type: :controller do
# Equivalent to being in spec/controllers
end
每個不同的規格類型中都有記錄不同的可用類型,例如控制器規格的文件。
更多資訊
已提取 activemodel mocks 支援
mock_model
和 stub_model
已被提取到 rspec-activemodel-mocks gem 中。
感謝 Thomas Holmes 進行提取並提供維護新 gem。
已移除 webrat 支援
Webrat 支援已移除。請改用 capybara。
匿名控制器改進
rspec-rails 長期以來允許您建立用於測試的匿名控制器。在 RSpec 3 中,它們獲得了一些改進。
- 預設情況下,它們將從所描述的類別繼承,而不是從
AppplicationController
繼承。可以使用infer_base_class_for_anonymous_controllers
設定選項停用此行為。 - 在「非標準」上下文(例如具有抽象父項或沒有
ApplicationController
的情況下)使用時,許多錯誤修正。如果您過去在匿名控制器方面遇到問題,現在是再次嘗試它們的好時機。
更多資訊
- 文件 - 匿名控制器
- rspec-rails #893 - 預設啟用推斷基底類別
- rspec-rails #905 - 修復匿名控制器路由輔助程式
- rspec-rails #924 - 不要假設存在 ApplicationController
最後的話
一如既往,每個子專案都有完整的變更日誌。
RSpec 3 是 RSpec 近 4 年來的首次主要發布。它代表了大量貢獻者的大量工作。
無論您如何使用 RSpec,我們都希望您喜歡這些新的變更。
感謝 Xavier Shay 協助撰寫這篇部落格文章,以及 Jon Rowe、Sam Phippen 和 Aaron Kromer 校對。