Cucumberで「ステップ定義」を探す

* Cucumber: http://cukes.info/
* Gherkin: https://github.com/cucumber/gherkin

## お題
Cucumberは、featureファイルに記述されたシナリオステップに対応する「ステップ定義（=実装）」をどうやって見つけてるのだろうか？というのが事の発端。
Cucumberのコードを読みつつ、指定したシナリオステップにマッピングされた「ステップ定義」を検索するRubyコードを書いてみる。

## コードを読む
[Cucumber::Runtime::SupportCode](https://github.com/cucumber/cucumber/blob/master/lib/cucumber/runtime/support_code.rb) クラスがマッチングのコア実装と思われる。ここを中心にざっと見てみる。

処理の流れとしては、

1. 「ステップ定義」ファイルを[読み込む](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime.rb#L42)
1. Walkerを[ビルド](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime.rb#L45)
1. featureファイルのシナリオステップを順次[実行](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/ast/features.rb#L24)
1. シナリオステップにマッチする「ステップ定義」を[検索](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime/support_code.rb#L132)
 * 複数マッチした場合、"[ベスト](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime/support_code.rb#L161)"な「ステップ定義」を検索
  * マッチした「ステップ定義」は[キャッシュ](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime/support_code.rb#L138)
1. 「ステップ定義」を[実行](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime/support_code.rb#L60)

## Rubyコード
Cucumber::Runtimeクラスに[step_match(step_name, name_to_report=nil)](https://github.com/cucumber/cucumber/blob/70ca40d0fe3d114a132eb5af380bf368c1a5c9b3/lib/cucumber/runtime.rb#L67)の定義があるので、これを叩けるように前提を揃えてやればよさそう。

1. Configurationの指定
1. 「ステップ定義」ファイルの読み込み

1.はコマンドライン引数からオプション指定で構築できるようにする。
2.は、private methodなので外部から実行できるようにする。

Rubyコードは[こちら](https://gist.github.com/arukoh/5651920)。featuresは[examples](https://github.com/cucumber/cucumber/tree/master/examples/)を使用。

```sh
$ ruby find_step_definition.rb "the result should be 1.5 on the screen" examples/i18n/en/features/
step definition
  file:	examples/i18n/en/features/step_definitons/calculator_steps.rb
	line:	22
	step:	{"source"=>"the result should be (.*) on the screen", "flags"=>""}
	arguments:
		{"offset"=>21, "val"=>"1.5"}
```

jaも検索してみる。

```sh
$ ruby find_step_definition.rb "1.5 を表示" examples/i18n/ja/features/
step definition
	file:	examples/i18n/ja/features/step_definitons/calculator_steps.rb
	line:	7
	step:	{"source"=>"(.*) を表示", "flags"=>""}
	arguments:
		{"offset"=>0, "val"=>"1.5"}
```

なかなかいい感じ。
ファイル名と行番号だけでなく、RDocやYARDみたくソースコードも表示できれば尚好し、かな。
yard-cucumberなんてのがあってYARD::CodeObjects::Baseを継承してがんばればやれそうな感触あったけど、ひとまずやりたいことはできたので終了。

## Appendix
Cucumber::Runtimeクラスに「ステップ定義」一覧をJSONファイルにダンプするメソッドが定義されていたのでついでに使ってみた。
Rubyコードは[こちら](https://gist.github.com/arukoh/5651913)。

```sh
$ ruby dump_step_definitions.rb --dotcucumber /path/to/.cucumber/ examples/i18n/en/features
write to /path/to/.cucumber/stepdefs.json
```

生成されたJSONファイル。

```js:JSON
[
  {
    "source": "I have entered (\\d+) into the calculator",
    "flags": "",
    "file_colon_line": "examples/i18n/en/features/step_definitons/calculator_steps.rb:14",
    "steps": [
      {
        "name": "I have entered 2 into the calculator",
        "args": [
          {
            "offset": 15,
            "val": "2"
          }
        ]
      },
      {
        "name": "I have entered 3 into the calculator",
        "args": [
          {
            "offset": 15,
            "val": "3"
          }
        ]
      }
    ]
  },
  {
    "source": "I press (\\w+)",
    "flags": "",
    "file_colon_line": "examples/i18n/en/features/step_definitons/calculator_steps.rb:18",
    "steps": [
      {
        "name": "I press divide",
        "args": [
          {
            "offset": 8,
            "val": "divide"
          }
        ]
      }
    ]
  },
  {
    "source": "the result should be (.*) on the screen",
    "flags": "",
    "file_colon_line": "examples/i18n/en/features/step_definitons/calculator_steps.rb:22",
    "steps": [
      {
        "name": "the result should be 1.5 on the screen",
        "args": [
          {
            "offset": 21,
            "val": "1.5"
          }
        ]
      },
      {
        "name": "the result should be <output> on the screen",
        "args": [
          {
            "offset": 21,
            "val": "<output>"
          }
        ]
      }
    ]
  }
]
```