聚合失败

RSpec::Expectations 提供了 aggregate_failures,这是一个 API,允许您对一组期望进行分组,并在一次查看所有失败,而不是在第一个失败时中止。RSpec::Core 在几个方面改进了此功能

* RSpec::Core provides much better failure output, adding code snippets and backtraces to the sub-failures, just like it does for any normal failure.
* RSpec::Core provides [metadata](../metadata/user-defined-metadata) integration for this feature. Each example that is tagged with `:aggregate_failures` will be wrapped in an `aggregate_failures` block. You can also use `config.define_derived_metadata` to apply this to every example automatically.

元数据形式非常方便,但可能不适用于具有多个不同步骤的端到端测试。例如,考虑一个 HTTP 客户端工作流程的规范,该工作流程(1)发出请求,(2)预期重定向,(3)遵循重定向,以及(4)预期特定响应。您可能希望 `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` 元数据

给定一个名为“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

我运行 `rspec spec/use_metadata_spec.rb`

那么它应该失败并列出所有失败

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` 在全局启用失败聚合

给定一个名为“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

我运行 `rspec spec/enable_globally_spec.rb`

那么它应该失败并列出所有失败

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` 在全局启用失败聚合

给定一个名为“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

我运行 `rspec spec/enable_globally_spec.rb`

那么它应该失败并列出所有失败

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

嵌套的失败聚合有效

给定一个名为“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

我运行 `rspec spec/nested_failure_aggregation_spec.rb`

那么它应该失败并列出所有失败

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

嵌套的失败聚合有效

给定一个名为“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

我运行 `rspec spec/nested_failure_aggregation_spec.rb`

那么它应该失败并列出所有失败

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

模拟期望失败也聚合

给定一个名为“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

我运行 `rspec spec/mock_expectation_failure_spec.rb`

那么它应该失败并列出所有失败

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

待处理示例与聚合失败良好集成

给定一个名为“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

我运行 `rspec spec/pending_spec.rb`

那么它应该通过并列出所有待处理示例

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

待处理示例与聚合失败良好集成

给定一个名为“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

我运行 `rspec spec/pending_spec.rb`

那么它应该通过并列出所有待处理示例

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