2014年5月17日土曜日

Cucumber(+Capybara)ステップ定義の実用的サンプル

現在のプロジェクトでは初めてBDDを採用して開発してます。具体的にはCucumberを使ってステップ定義を書いています。

Cucumberについて調べると、確かにサンプルとしてGherkinによるステップ定義の例は色々出てくるんですけれども、日本語によるステップ定義で実用的というかそのまま実務に使えるサンプルってネット上に出ているものが少ない気がします。実務的じゃないものは沢山見つかるんですけどね。

ということで、BDDはまだ初心者といえる私ですけれども、実務ですぐに使える日本語で書いたGherkinによるステップ定義のサンプルを晒してみたいと思います。なお、具体的なテストコードの記述にはCapybaraを利用しています。

ちょっと長いですが、実用的なサンプルということでご理解ください。ではどうぞ。

もし(/^".*?\((.*?)\)" 画面にアクセス(?:し(、)|した)( debug)?$/) do |t_path, t_eos, debug|
  show_debug_info if debug
  visit t_path
  puts '' if t_eos.blank?
end

もし(/^HTTPステータスコードが "(\d{3})" で(?:あり(、)|ある)( debug)?$/) do |t_code, t_eos, debug|
  show_debug_info if debug
  expect(page.status_code).to eq(t_code.to_i)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" 欄に "(.*?)" と入力(?:し(、)|した)( debug)?$/) do |t_locator, t_with, t_eos, debug|
  show_debug_info if debug
  page.fill_in(t_locator, :with => t_with)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" ボタンを押下(?:し(、)|した)( debug)?$/) do |t_locator, t_eos, debug|
  show_debug_info if debug
  page.click_button(t_locator)
  puts '' if t_eos.blank?
end

ならば(/^ページタイトルが "(.*?)" で(?:あり(、)|あること)( debug)?$/) do |t_title, t_eos, debug|
  show_debug_info if debug
  expect(page.title).to eq(t_title)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" が存在(?:し(、)|すること)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  expect(page).to have_css(t_css, :wait => t_async ? async_time : 0)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" が存在(?:せず(、)|しないこと)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  expect(page).to have_no_css(t_css, :wait => t_async ? async_time : 0)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" 画面に遷移(?:し(、)|すること)( debug)?$/) do |t_path, t_eos, debug|
  show_debug_info if debug
  expect(page.current_path).to eq(t_path)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:され(、)|されていること)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_eos, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).to eq(t_text)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:されず(、)|されていないこと)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_eos, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).not_to eq(t_text)
  puts '' if t_eos.blank?
end

def async_time
  Capybara.default_wait_time
end

def show_debug_info
  puts 'Respnse Headers: ' + page.response_headers.to_s
  puts 'Status Code: ' + page.status_code.to_s
  puts 'Current Url: ' + page.current_url
  puts "HTML:\n" + page.html
end

以上になります。いくつかポイントを解説します。

URLやボタンや入力欄などの条件はカッコ内に記述するようにしているものがあります。例えば画面へのアクセスは "画面名(URL)" という記述です。他にも "ボタン名(HTMLのID)" とか、 "探したい要素の呼び名(CSSセレクタ)" とかですね。これはテストの見た目のわかりやすさと同時に、ステップ定義を使いまわせることを実現するためです。

各ステップは末尾に " debug" (半角スペースの後ろにdebug) とつけると、そのステップ実行直前のレスポンスについて、レスポンスヘッダ、ステータスコード、URL、HTMLコードをデバッグ情報として出力します。テストをしながら失敗したステップの時の状態が知りたいことってあると思うので、こうしてステップ定義にデバッグ情報の出力オプションを付けておけば便利ですよね。

あと、末尾に "(非同期)" とつける事のできるステップもあります。これは、Capybaraの機能で、例えばレスポンス中に指定した要素が存在するか確認するコードの場合、見つからなくても非同期処理の可能性を考慮して一定時間待ってから再度探してくれる、という機能を切り替えるためのものです。Capybaraではデフォルトで毎回2秒待ってくれるのですが、非同期を想定していない場合、この待ち時間は無駄です。テスト駆動をする場合はテスト失敗を頻繁に起こすのでなおさら無駄な待ち時間は無くしたいです。ということで、末尾に "(非同期)" と付けない場合はCapybaraの待ち時間を0秒に設定することで、無駄な待ち時間が発生しないようにしています。

あと、細かい点になりますが、ステップ定義文の最後の語尾に読点(、)をつける書き方とそうでない書き方の2通りできるようにしてまして、後者(読点なし)の場合には、次の行に空行を出力するようにしています。これは、Cucumberの実行時に、ステップ定義のソースコード上での空白行を無視して出力してしまって見辛いので、強制的に空行を再現させる目的で付けています。言葉で説明するとわかりにくいと思いますが実行結果を見れば一発でわかると思います。こんな風になります。


空行が出力されるので、結果がぐっと見やすくなりますね。

もしかしたら今後開発を進めるうちにまた色々変わってきたらアップデート版を書くかもしれませんし、書かないかもしれませんが、参考になれば幸いです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

0 件のコメント:

コメントを投稿