メインコンテンツまでスキップ

Bisect(二分探索)

RSpecの--order random--seedオプションは、他の1つ以上の例が最初に実行された場合にのみ失敗する揺れる例を浮かび上がらせるのに役立ちます。失敗を引き起こす正確な例の組み合わせを特定するのは非常に困難です。--bisectフラグは、この問題を解決するのに役立ちます。

--seedと他のオプションに加えて--bisectオプションを渡すと、RSpecは繰り返しスイートのサブセットを実行して、同じ失敗を再現する最小の例のセットを特定します。

二分探索の実行中にいつでもctrl-cを押すと、それまでに発見された最小の再現コマンドが表示されます。

詳細な出力を取得するには(特にbisectのバグを報告する場合に便利です)、--bisect=verboseを使用します。

背景

次の内容で「lib/calculator.rb」という名前のファイルがあるとします:

class Calculator
def self.add(x, y)
x + y
end
end

次の内容で「spec/calculator_1_spec.rb」という名前のファイルがあるとします:

require 'calculator'

RSpec.describe "Calculator" do
it 'adds numbers' do
expect(Calculator.add(1, 2)).to eq(3)
end
end

「spec/calculator_2_spec.rb」から「spec/calculator_9_spec.rb」までのファイルには、それぞれ関係のないパスするスペックが含まれています。

次の内容で「spec/calculator_10_spec.rb」という名前のファイルがあるとします:

require 'calculator'

RSpec.describe "Monkey patched Calculator" do
it 'does screwy math' do
# monkey patching `Calculator` affects examples that are
# executed after this one!
def Calculator.add(x, y)
x - y
end

expect(Calculator.add(5, 10)).to eq(-5)
end
end

--bisectフラグを使用して順序依存性の最小再現ケースを作成する

次のコマンドを実行すると:

rspec --seed 1234

出力には「10 examples, 1 failure」という文言が含まれるはずです。

次のコマンドを実行すると:

rspec --seed 1234 --bisect

bisectは、次のような出力で成功するはずです:

Bisect started using options: "--seed 1234"
Running suite to find failures... (0.16755 seconds)
Starting bisect with 1 failing example and 9 non-failing examples.
Checking that failure(s) are order-dependent... failure appears to be order-dependent

Round 1: bisecting over non-failing examples 1-9 .. ignoring examples 6-9 (0.30166 seconds)
Round 2: bisecting over non-failing examples 1-5 .. ignoring examples 4-5 (0.30306 seconds)
Round 3: bisecting over non-failing examples 1-3 .. ignoring example 3 (0.33292 seconds)
Round 4: bisecting over non-failing examples 1-2 . ignoring example 1 (0.16476 seconds)
Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 1.26 seconds.

The minimal reproduction command is:
rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] --seed 1234

次のコマンドを実行すると:

rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] --seed 1234

出力には「2 examples, 1 failure」という文言が含まれるはずです。

Ctrl-Cを使用してbisectを途中で中断し、それまでに発見された最小のコマンドを取得することができます

次のコマンドを実行し、途中でctrl-cで中断すると:

rspec --seed 1234 --bisect

bisectは、次のような出力で失敗するはずです:

Bisect started using options: "--seed 1234"
Running suite to find failures... (0.17102 seconds)
Starting bisect with 1 failing example and 9 non-failing examples.
Checking that failure(s) are order-dependent... failure appears to be order-dependent

Round 1: bisecting over non-failing examples 1-9 .. ignoring examples 6-9 (0.32943 seconds)
Round 2: bisecting over non-failing examples 1-5 .. ignoring examples 4-5 (0.3154 seconds)
Round 3: bisecting over non-failing examples 1-3 .. ignoring example 3 (0.2175 seconds)

Bisect aborted!

The most minimal reproduction command discovered so far is:
rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_3_spec.rb[1:1] --seed 1234

次のコマンドを実行すると:

rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_3_spec.rb[1:1] --seed 1234

その後、出力には「3つの例、1つの失敗」と表示される必要があります。

--bisect=verboseを使用して詳細なデバッグモードを有効にする

rspec --seed 1234 --bisect=verboseを実行すると、

bisectは次のような出力で成功する必要があります。

Bisect started using options: "--seed 1234" and bisect runner: :fork
Running suite to find failures... (0.16528 seconds)
- Failing examples (1):
- ./spec/calculator_1_spec.rb[1:1]
- Non-failing examples (9):
- ./spec/calculator_10_spec.rb[1:1]
- ./spec/calculator_2_spec.rb[1:1]
- ./spec/calculator_3_spec.rb[1:1]
- ./spec/calculator_4_spec.rb[1:1]
- ./spec/calculator_5_spec.rb[1:1]
- ./spec/calculator_6_spec.rb[1:1]
- ./spec/calculator_7_spec.rb[1:1]
- ./spec/calculator_8_spec.rb[1:1]
- ./spec/calculator_9_spec.rb[1:1]
Checking that failure(s) are order-dependent..
- Running: rspec ./spec/calculator_1_spec.rb[1:1] --seed 1234 (n.nnnn seconds)
- Failure appears to be order-dependent
Round 1: bisecting over non-failing examples 1-9
- Running: rspec ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_6_spec.rb[1:1] ./spec/calculator_7_spec.rb[1:1] ./spec/calculator_8_spec.rb[1:1] ./spec/calculator_9_spec.rb[1:1] --seed 1234 (0.15302 seconds)
- Running: rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_2_spec.rb[1:1] ./spec/calculator_3_spec.rb[1:1] ./spec/calculator_4_spec.rb[1:1] ./spec/calculator_5_spec.rb[1:1] --seed 1234 (0.19708 seconds)
- Examples we can safely ignore (4):
- ./spec/calculator_6_spec.rb[1:1]
- ./spec/calculator_7_spec.rb[1:1]
- ./spec/calculator_8_spec.rb[1:1]
- ./spec/calculator_9_spec.rb[1:1]
- Remaining non-failing examples (5):
- ./spec/calculator_10_spec.rb[1:1]
- ./spec/calculator_2_spec.rb[1:1]
- ./spec/calculator_3_spec.rb[1:1]
- ./spec/calculator_4_spec.rb[1:1]
- ./spec/calculator_5_spec.rb[1:1]
Round 2: bisecting over non-failing examples 1-5
- Running: rspec ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_4_spec.rb[1:1] ./spec/calculator_5_spec.rb[1:1] --seed 1234 (0.15836 seconds)
- Running: rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_2_spec.rb[1:1] ./spec/calculator_3_spec.rb[1:1] --seed 1234 (0.19065 seconds)
- Examples we can safely ignore (2):
- ./spec/calculator_4_spec.rb[1:1]
- ./spec/calculator_5_spec.rb[1:1]
- Remaining non-failing examples (3):
- ./spec/calculator_10_spec.rb[1:1]
- ./spec/calculator_2_spec.rb[1:1]
- ./spec/calculator_3_spec.rb[1:1]
Round 3: bisecting over non-failing examples 1-3
- Running: rspec ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_2_spec.rb[1:1] --seed 1234 (0.21028 seconds)
- Running: rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] ./spec/calculator_3_spec.rb[1:1] --seed 1234 (0.1975 seconds)
- Examples we can safely ignore (1):
- ./spec/calculator_2_spec.rb[1:1]
- Remaining non-failing examples (2):
- ./spec/calculator_10_spec.rb[1:1]
- ./spec/calculator_3_spec.rb[1:1]
Round 4: bisecting over non-failing examples 1-2
- Running: rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] --seed 1234 (0.17173 seconds)
- Examples we can safely ignore (1):
- ./spec/calculator_3_spec.rb[1:1]
- Remaining non-failing examples (1):
- ./spec/calculator_10_spec.rb[1:1]
Bisect complete! Reduced necessary non-failing examples from 9 to 1 in 1.47 seconds.

The minimal reproduction command is:
rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] --seed 1234

rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] --seed 1234を実行すると、

出力には「2つの例、1つの失敗」と表示される必要があります。

設定オプションを使用してbisectランナーを選択する

spec/spec_helper.rbという名前のファイルが次の内容である場合:

RSpec.configure do |c|
c.bisect_runner = :shell
end

および.rspecという名前のファイルが次の内容である場合:

--require spec_helper

rspec --seed 1234 --bisect=verboseを実行すると、

bisectは次のような出力で成功する必要があります。

Bisect started using options: "--seed 1234" and bisect runner: :shell
# ...
The minimal reproduction command is:
rspec ./spec/calculator_10_spec.rb[1:1] ./spec/calculator_1_spec.rb[1:1] --seed 1234