失敗の集約
RSpec::Expectationsは、aggregate_failures
を提供しており、このAPIを使用すると、最初の失敗で中断するのではなく、一度にすべての失敗をグループ化して表示することができます。 RSpec::Coreは、この機能をいくつかの方法で改善しています。
- RSpec::Coreは、コードスニペットとバックトレースをサブ失敗に追加することで、失敗の出力を大幅に向上させます。
- RSpec::Coreは、この機能のためのメタデータの統合を提供します。
:aggregate_failures
とタグ付けされた各例は、aggregate_failures
ブロックでラップされます。また、config.define_derived_metadata
を使用して、これをすべての例に自動的に適用することもできます。
メタデータ形式は非常に便利ですが、複数の異なるステップを持つエンドツーエンドテストには適していない場合があります。たとえば、(1)リクエストを行い、(2)リダイレクトを期待し、(3)リダイレクトに従い、(4)特定のレスポンスを期待するHTTPクライアントのワークフローの仕様を考えてみてください。もしexpect(response.status).to be_between(300, 399)
の期待が失敗した場合、次のステップ(リダイレクトに従う)を実行することはできませんので、即座に中断したいと思うでしょう。このような状況では、テストワークフローの各ステップを表す期待をラップするために、aggregate_failures
ブロック形式を使用することをお勧めします。
背景
以下の内容で "lib/client.rb" という名前のファイルがあるとします:
Response = Struct.new(:status, :headers, :body)
class Client
def self.make_request(url='/')
Response.new(404, { "Content-Type" => "text/plain" }, "Not Found")
end
end
aggregate_failures
ブロック形式の使用
以下の内容で "spec/use_block_form_spec.rb" という名前のファイルがあるとします:
require 'client'
RSpec.describe Client do
after do
# this should be appended to failure list
expect(false).to be(true), "after hook failure"
end
around do |ex|
ex.run
# this should also be appended to failure list
expect(false).to be(true), "around hook failure"
end
it "returns a successful response" do
response = Client.make_request
aggregate_failures "testing response" do
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
end
rspec spec/use_block_form_spec.rb
を実行すると
すべての失敗が表示されるはずです:
Failures:
1) Client returns a successful response
Got 3 failures:
1.1) Got 3 failures from failure aggregation block "testing response".
# ./spec/use_block_form_spec.rb:18
# ./spec/use_block_form_spec.rb:10
1.1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/use_block_form_spec.rb:19
1.1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1 +1 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/use_block_form_spec.rb:20
1.1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/use_block_form_spec.rb:21
1.2) Failure/Error: expect(false).to be(true), "after hook failure"
after hook failure
# ./spec/use_block_form_spec.rb:6
# ./spec/use_block_form_spec.rb:10
1.3) Failure/Error: expect(false).to be(true), "around hook failure"
around hook failure
# ./spec/use_block_form_spec.rb:12
aggregate_failures
ブロック形式の使用
以下の内容で "spec/use_block_form_spec.rb" という名前のファイルがあるとします:
require 'client'
RSpec.describe Client do
after do
# this should be appended to failure list
expect(false).to be(true), "after hook failure"
end
around do |ex|
ex.run
# this should also be appended to failure list
expect(false).to be(true), "around hook failure"
end
it "returns a successful response" do
response = Client.make_request
aggregate_failures "testing response" do
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
end
rspec spec/use_block_form_spec.rb
を実行すると
すべての失敗が表示されるはずです:
Failures:
1) Client returns a successful response
Got 3 failures:
1.1) Got 3 failures from failure aggregation block "testing response".
# ./spec/use_block_form_spec.rb:18
# ./spec/use_block_form_spec.rb:10
1.1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/use_block_form_spec.rb:19
1.1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1,2 +1,2 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/use_block_form_spec.rb:20
1.1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/use_block_form_spec.rb:21
1.2) Failure/Error: expect(false).to be(true), "after hook failure"
after hook failure
# ./spec/use_block_form_spec.rb:6
# ./spec/use_block_form_spec.rb:10
1.3) Failure/Error: expect(false).to be(true), "around hook failure"
around hook failure
# ./spec/use_block_form_spec.rb:12
:aggregate_failures
メタデータを使用する
Given "spec/use_metadata_spec.rb" という名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.describe Client do
it "follows a redirect", :aggregate_failures do
response = Client.make_request
expect(response.status).to eq(302)
expect(response.body).to eq('{"message":"Redirect"}')
redirect_response = Client.make_request(response.headers.fetch('Location'))
expect(redirect_response.status).to eq(200)
expect(redirect_response.body).to eq('{"message":"OK"}')
end
end
When rspec spec/use_metadata_spec.rb
を実行すると
Then 失敗し、すべての失敗がリストされる:
Failures:
1) Client follows a redirect
Got 2 failures and 1 other error:
1.1) Failure/Error: expect(response.status).to eq(302)
expected: 302
got: 404
(compared using ==)
# ./spec/use_metadata_spec.rb:7
1.2) Failure/Error: expect(response.body).to eq('{"message":"Redirect"}')
expected: "{\"message\":\"Redirect\"}"
got: "Not Found"
(compared using ==)
# ./spec/use_metadata_spec.rb:8
1.3) Failure/Error: redirect_response = Client.make_request(response.headers.fetch('Location'))
KeyError:
key not found: "Location"
# ./spec/use_metadata_spec.rb:10
# ./spec/use_metadata_spec.rb:10
define_derived_metadata
を使用してグローバルに失敗の集約を有効にする
Given "spec/enable_globally_spec.rb" と いう名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.configure do |c|
c.define_derived_metadata do |meta|
meta[:aggregate_failures] = true
end
end
RSpec.describe Client do
it "returns a successful response" do
response = Client.make_request
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
When rspec spec/enable_globally_spec.rb
を実行すると
Then 失敗し、すべての失敗がリストされる:
Failures:
1) Client returns a successful response
Got 3 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/enable_globally_spec.rb:13
1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1 +1 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/enable_globally_spec.rb:14
1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/enable_globally_spec.rb:15
define_derived_metadata
を使用してグローバルに失敗の集約を有効にする
Given "spec/enable_globally_spec.rb" という名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.configure do |c|
c.define_derived_metadata do |meta|
meta[:aggregate_failures] = true
end
end
RSpec.describe Client do
it "returns a successful response" do
response = Client.make_request
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
When rspec spec/enable_globally_spec.rb
を実行すると
Then 失敗し、すべての失敗がリストされる:
Failures:
1) Client returns a successful response
Got 3 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/enable_globally_spec.rb:13
1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1,2 +1,2 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/enable_globally_spec.rb:14
1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/enable_globally_spec.rb:15
ネストされた失敗の集約が機能する
Given "spec/nested_failure_aggregation_spec.rb" という名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.describe Client do
it "returns a successful response", :aggregate_failures do
response = Client.make_request
expect(response.status).to eq(200)
aggregate_failures "testing headers" do
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.headers).to include("Content-Length" => "21")
end
expect(response.body).to eq('{"message":"Success"}')
end
end
When rspec spec/nested_failure_aggregation_spec.rb
を実行すると
Then 失敗し、すべての失敗がリストされる:
Failures:
1) Client returns a successful response
Got 3 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/nested_failure_aggregation_spec.rb:7
1.2) Got 2 failures from failure aggregation block "testing headers".
# ./spec/nested_failure_aggregation_spec.rb:9
1.2.1) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1 +1 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/nested_failure_aggregation_spec.rb:10
1.2.2) Failure/Error: expect(response.headers).to include("Content-Length" => "21")
expected {"Content-Type" => "text/plain"} to include {"Content-Length" => "21"}
Diff:
@@ -1 +1 @@
-"Content-Length" => "21",
+"Content-Type" => "text/plain",
# ./spec/nested_failure_aggregation_spec.rb:11
1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/nested_failure_aggregation_spec.rb:14
ネストされた失敗の集約が機能する
Given "spec/nested_failure_aggregation_spec.rb" という名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.describe Client do
it "returns a successful response", :aggregate_failures do
response = Client.make_request
expect(response.status).to eq(200)
aggregate_failures "testing headers" do
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.headers).to include("Content-Length" => "21")
end
expect(response.body).to eq('{"message":"Success"}')
end
end
When rspec spec/nested_failure_aggregation_spec.rb
を実行すると
Then 失敗し、すべての失敗がリストされる:
Failures:
1) Client returns a successful response
Got 3 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/nested_failure_aggregation_spec.rb:7
1.2) Got 2 failures from failure aggregation block "testing headers".
# ./spec/nested_failure_aggregation_spec.rb:9
1.2.1) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1,2 +1,2 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/nested_failure_aggregation_spec.rb:10
1.2.2) Failure/Error: expect(response.headers).to include("Content-Length" => "21")
expected {"Content-Type" => "text/plain"} to include {"Content-Length" => "21"}
Diff:
@@ -1,2 +1,2 @@
-"Content-Length" => "21",
+"Content-Type" => "text/plain",
# ./spec/nested_failure_aggregation_spec.rb:11
1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/nested_failure_aggregation_spec.rb:14
モックの期待失敗も集約される
Given "spec/mock_expectation_failure_spec.rb" という名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.describe "Aggregating Failures", :aggregate_failures do
it "has a normal expectation failure and a message expectation failure" do
client = double("Client")
expect(client).to receive(:put).with("updated data")
allow(client).to receive(:get).and_return(Response.new(404, {}, "Not Found"))
response = client.get
expect(response.status).to eq(200)
end
end
When rspec spec/mock_expectation_failure_spec.rb
を実行すると
Then 失敗し、すべての失敗がリストされる:
Failures:
1) Aggregating Failures has a normal expectation failure and a message expectation failure
Got 2 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/mock_expectation_failure_spec.rb:10
1.2) Failure/Error: expect(client).to receive(:put).with("updated data")
(Double "Client").put("updated data")
expected: 1 time with arguments: ("updated data")
received: 0 times
# ./spec/mock_expectation_failure_spec.rb:6
保留中のテストは失敗の集約と正しく統合される
Given "spec/pending_spec.rb" という名前のファイルがある場合、以下の内容である:
require 'client'
RSpec.describe Client do
it "returns a successful response", :aggregate_failures do
pending "Not yet ready"
response = Client.make_request
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
When rspec spec/pending_spec.rb
を実行すると
Then パスし、すべての保留中の例がリストされる:
Pending: (Failures listed here are expected and do not affect your suite's status)
1) Client returns a successful response
# Not yet ready
Got 3 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/pending_spec.rb:8
1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1 +1 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/pending_spec.rb:9
1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/pending_spec.rb:10
ペンディングは集約された失敗と正しく統合されます
Given ファイル名が "spec/pending_spec.rb" のファイルがあるとき:
require 'client'
RSpec.describe Client do
it "returns a successful response", :aggregate_failures do
pending "Not yet ready"
response = Client.make_request
expect(response.status).to eq(200)
expect(response.headers).to include("Content-Type" => "application/json")
expect(response.body).to eq('{"message":"Success"}')
end
end
When rspec spec/pending_spec.rb
を実行すると
Then ペンディングの例がすべて表示され、パスするはずです:
Pending: (Failures listed here are expected and do not affect your suite's status)
1) Client returns a successful response
# Not yet ready
Got 3 failures:
1.1) Failure/Error: expect(response.status).to eq(200)
expected: 200
got: 404
(compared using ==)
# ./spec/pending_spec.rb:8
1.2) Failure/Error: expect(response.headers).to include("Content-Type" => "application/json")
expected {"Content-Type" => "text/plain"} to include {"Content-Type" => "application/json"}
Diff:
@@ -1,2 +1,2 @@
-"Content-Type" => "application/json",
+"Content-Type" => "text/plain",
# ./spec/pending_spec.rb:9
1.3) Failure/Error: expect(response.body).to eq('{"message":"Success"}')
expected: "{\"message\":\"Success\"}"
got: "Not Found"
(compared using ==)
# ./spec/pending_spec.rb:10