状況
TurnipでGivenScenario
したい!
Turnip向けに書いたGherkinシナリオが、他のシナリオに書いたステップで実現される状況を前提としている。
このため、前に書いたシナリオAを今書こうとしているシナリオBにおいて「前提」ステップとして再利用したい。
こんな風に書けたらいいな
機能: Hoge
背景:
前提 Fugaする
シナリオ: Aを登録
もし Piyoする
ならば PiyoPiyo
シナリオ: Bを登録
前提 「Aを登録」しておく
もし Hogeraする
ならば HogeraHogeHoge
解
Cucumberの各ホスト言語における実装は知らんけど、Turnipの場合割とシンプルに内部でRSpecのdescribe
... context
... it
構造にマップされているため頑張ればなんとかなる。
このfeatureに対応して内部で生成されているのはだいたいこういうコードだ。
feature = Turnip::Builder.build(feature_file)
describe "機能 Hoge" do
background_steps = feature.backgrounds.map(&:steps).flatten
before {
background_steps.each do |step|
run_step(feature_file, step)
end
}
feature.scenarios.each do |scenario|
describe scenario.name do
# "Bを登録 前提 「Aを登録」しておく -> もし Hogeraする -> ならば HogeraHogeHoge"みたいなやつ
description = (略)
it description do
scenario.steps.each do |step|
run_step(filename, step)
end
end
end
end
end
我々がstepの定義を書くときはこのrun_step
から呼び出された中にいる。従って、現在のit
ブロックへのアクセスだけできればscenario
にもfeature
にもアクセスできる。
これを利用すると次のように書ける。
step '「:name」しておく' do |name|
context = RSpec.current_example.metadata[:block].binding
current_feature = context.local_variable_get(:feature)
filename = context.local_variable_get(:filename)
scenario = current_feature.scenarios.find {|scenario| scenario.name == name }
scenario.steps.each do |step|
run_step(filename, step)
end
end
今後の課題
残念ながら、このままでは他のfeature
に属するシナリオにアクセスすることはできない。
ローカル変数feature
はそのfeatureのためのdescribe
を構築しているブロック内にしか存在しないので、そのままでは他のfeatureを現在のit
内から参照する術はないのである。
ただし、RSpecがdescribe
をネストしたRSpec::Core::ExampleGroup
のサブクラスにマッピングする内部APIはもう何年も安定していて、そこそこ安心して利用できそうだ。したがって、ObjectSpace.each_object
でそれっぽいExampleGroup
インスタンスを浚って目標のfeatureに相当するものを特定すれば、なんとかする余地はありそうだ。
警告!
Cucumberにおける公式の見解は「そういう機能はサポートしない」らしい。代わりに、Cucumberのホスト言語で高度なヘルパーメソッドを書いてそれをステップ定義で利用したり、前提状況を再現するWorld
を作ったりするのが推奨されている。
本稿はそれでもなんとかしたいという話であるが、以下に見るように(少なくともcucumberでは)あまり歓迎されていない手法である。
I initially thought this was a great feature...
However... in the longer term it produces scenarios that are hard to maintain and understand
this is a common request that we are not going to implement
We implemented this feature (we call it "nested steps") in the Ruby Cucumber, and it's been hard to maintain, and led to some very complex dependencies in people's test code.