Fest + JRuby + Cucumber で Swing のテストをする - 破

何も破ってない気もするけれど。

Step03 最初のステップ - 画面を見つける

cucumber の詳細についてはググってもらうとして。

step を作成する

test-feature/step_definitions/ticket_viewer_steps.ja.rb

Given /^"([^"]*)" が表示されていること$/ do |window_name|
  case window_name
  when /メイン画面/
    component_name = "MainFrame"
  else
    component_name = window_name
  end
  
  #====================================================================================
  # fest の API - WindowFinder を使って、画面を見つける
  # 変数の前に @ をつけておけば、他のところでも参照できる。(仕組みはイマイチ分からんが)
  #====================================================================================
  @current_window = FestFinder::WindowFinder.findFrame(component_name).using($my_robot)
end

タイトルで検索するメソッドはない(自作すればできる)ので、タイトルを画面名に変換して検索。
WindowFinder.findFrame を呼ぶだけでは画面は見つけられなくって、 using でロボットを指定する必要がある。

画面が見つからない場合、 fest が以下のようなエラーを吐くので、その辺のコードはいらない。

そういえば、ロボットは実行中のテストで唯一のほうがいいと、どっかのページで見たことがあったぞ…。
WindowFinder, ロボットを唯一にするために、 env.rb を修正する。

test-feature/support/env.rb

#=======================================================================================
# Java のライブラリを使えるようにする
#=======================================================================================
include java

#=======================================================================================
# festの名前空間を定義
#=======================================================================================
# クラスパスを通して
Dir["#{File.dirname(__FILE__)}/../../lib/fest\*.jar"].each { |jar| require jar }
# 名前空間を定義する
module FestFeature
  include_package 'org.fest.swing.fixture'
end
module FestFinder
  include_package 'org.fest.swing.finder'
end
module FestCore
  include_package 'org.fest.swing.core'
end

#=======================================================================================
# 製品の名前空間を定義
#=======================================================================================
# こっちも同様に
require "#{File.dirname(__FILE__)}/../../dist/AutomatedUITestingJava.jar"
module MyPrjTicketViewer
  include_package 'local.myproject.ticket_viewer'
end

#=======================================================================================
# このテスト用のロボットを作成する
#=======================================================================================
$my_robot = FestCore::BasicRobot.robotWithNewAwtHierarchy

#=======================================================================================
# 画面を作って、表示する
#=======================================================================================
# Fixture の詳細は http://easytesting.org/swing/apidocs/index.html 辺を参照のこと
main_frame = FestFeature::FrameFixture.new $my_robot, MyPrjTicketViewer::MainFrame.new
main_frame.show

#=======================================================================================
# Cucumber のテストが終わった後にやること
#=======================================================================================
at_exit do
  # fest が使った資源をきれいさっぱり掃除する
  main_frame.cleanUp
end
実行する
> cucumber ..\test-feature --format html --out cucumber.html

無事メイン画面を見つけて、ステップが成功。

整理する

Webアプリのテストとかだと support/paths.rb とかで画面名とURLの対応を取ってる。
それをまねして。

test-feature/support/window_name.rb

def window_name_to(window_title)
  case window_title
  when /メイン画面/
    ret_val = "MainFrame"
  else
    raise "画面名をマッピングできませんでした #{window_title}"
  end
  return ret_val
end

変数名がちょい変わったので、steps 側も修正。

Step04 テキストボックスに値を入力する

次のステップ。さっきと同じように step_definitions/ticket_viewer_steps_ja.rb にコードを追加すればOK。
fest の FrameFixture クラスにある textBox メソッドにコントロールの名前を渡せばOK

test-feature/step_definitions/ticket_viewer_steps_ja.rb

When /^"([^"]*)" に "([^"]*)" を入力する$/ do |name, value|
  case name
  when /URL/
    control_name = "ctlURL"
  else
    raise "コントロール名をマッピングできませんでした #{name}"
  end

  #====================================================================================
  # textbox に値を入力する場合は setText を使う (enterText だと http:// が http+// になってしまった…)
  #====================================================================================
  @current_window.textBox(control_name).setText(value)
end
実行する

おや? http:// じゃなくって http+// になるぞ?
setText にすると http:// になるな。なぜだ…。気持ち悪いけど setText でいくことにする。

整理する

やっぱり control_name.rb があったほうがいいかなぁ。

test-feature/support/control_name.rb

詳細は略。

Step05 ボタンをクリックする

同じように FrameFixture の button メソッドでボタンを見つけて、click メソッドを呼べばいい。

test-feature/step_definitions/ticket_viewer_steps_ja.rb

When /^"([^"]*)" ボタンをクリックする$/ do |name|
  control_name = control_name_to(name)
  
  #====================================================================================
  # click でボタンをクリック。説明なんていらないよな。
  #====================================================================================
  @current_window.button(control_name).click
end

コントロール名への変換は control_name.rb で。

実行する

押してるっぽい。ポーズとかできると良いんだけども…。

Step06 値を検査する

テキストで書いた表を使って値の検査ができるのは、 cucumber ならでは。
同じように step_definitions/ticket_viewer_steps_ja.rb に追記する。

test-feature/step_definitions/ticket_viewer_steps_ja.rb

Then /^リスト "([^"]*)" に以下が表示されていること:$/ do |name, table|
  control_name = control_name_to(name)
  
  #====================================================================================
  # FrameFixture.list で JList を見つける。
  # contens メソッドはリストの内容を String の配列にしてくれる。
  # Cucumber::Ast::Table の diff! を使えるよう、[["hoge"],["fuga"]] のように配列の配列にする
  # 多分もっと良い方法があるに違いない。
  #====================================================================================
  list = @current_window.list(control_name).contents.map {|project_name|[project_name]}

  #====================================================================================
  # Cucumber::Ast::Table の diff! を使って簡単に内容を比較できる(こりゃ楽だ)
  #====================================================================================
  table.diff!(list)
end

ここはちょっとだけ説明。上記のように書いておくと、

    ならば リスト "プロジェクト一覧" に以下が表示されていること:
      |r-labs        |
      |Hudson        |
      |IssueExtension|
      |WikiExtension |

の内容を見て name に "プロジェクト一覧" が、 table に表の部分が読み込まれる。
table の正体 Cucumber::Ast::Table クラス。

このクラス、 diff! メソッドで内容を比較してくれる。便利!
評価したい内容を 配列の配列、または ハッシュの形にして、 diff! メソッドに渡せば良い。

table の内容と違っていると...

のように違ってる場所を示してくれる。さらに便利!