redmine のテストを Cucumber と Selenium2.0 WebDriver を使って firefox と IE の2つのブラウザでやってみる

というわけで次は cucumber と組み合わせてみる。

構成

redmine <--> firefox <--> selenium <--> capybara <--> cucumber

になるのかな?

準備するもの

さっきの続きなので、追加したものだけ。

Selenium陣営

rails, cucumber-rails は雛形作りたいためだけのもの…。

Redmine陣営

特になし。

Capybara 使った cucuber の雛形を持ってくる

Railsアプリを準備
> rails new test-app
Gemfile に cucumber-rails, capybara を追加

group test に突っ込んだ。

group :test do
  # Pretty printed test output
  gem 'turn', :require => false
  gem 'cucumber-rails'
  gem 'capybara'
end
雛形を作る
> cd test-app
> ruby script/rails generate cucumber:install --capybara

雛形だけがほしかったので、
test-app\features にあるファイルとフォルダを、どこぞにコピーする。

+ using-cucumber
  + step_definitions
    + web_steps.rb
  + support
    + env.rb
    + paths.rb
    + selectors.rb

動くようにコードを改変

rails アプリの中で動かすならこれでいいのだけれど、
今回は外から rails のテストをするので、改変しないとエラーが出て動かない。
※ 多分ちゃんとやり方があるに違いない。

env.rb

# -*- encoding: utf-8 -*-
require "Capybara"
require "Capybara/cucumber"
require 'test/unit/assertions'
World(Test::Unit::Assertions)

Capybara.app_host = "http://127.0.0.1:3000"
Capybara.default_driver = :selenium
Capybara.javascript_driver = :selenium
Capybara.default_wait_time = 2
Capybara.add_selector(:name) do
  xpath { |name| XPath.descendant[XPath.attr(:name) == name.to_s] }
  match { |value| value.is_a?(Symbol) }
end
Capybara.add_selector(:class_name) do
  xpath { |class_name| XPath.descendant[XPath.attr(:class) == class_name.to_s] }
  match { |value| value.is_a?(Symbol) }
end

feature をもくもくと書く

web_steps.rb も成長させる
できあがった feature はこんな感じ。

redmine_walpurugis_night.feature

Feature: redmine_walpurgis_nights
  In order to extend life of universe
  As Incubator
  wants more energy.

  Scenario: Register new issue
    Given I go to toppage
    When  I follow "ログイン"
     And  I fill in "username" with "qb"
	 And  I fill in "password" with "homuhomu"
	 And  I press "login"
    
	When  I select "Walpurgis Night"
	 And  I follow "New issue"
	
	When  I select "Feature" from "issue_tracker_id"
     And  I fill in "issue_subject" with "As Incubator, I want magical girl."
	 And  I fill in "issue_description" with "foobaa"
	 And  I select "High" from "issue_priority_id"
	 And  I select "qb incubator" from "issue_assigned_to_id"
	 And  I press "commit"
	 
	When  I follow "Issues"
	Then  I should see following data at the head of issue list:
	  |tracker|status|priority|subject                           |assigned_to |
	  |Feature|New   |High    |As Incubator, I want magical girl.|qb incubator|
詰まったところ。
  • テーブルを見つけて、目的の値を取り出す(WebDriver と同じような操作で大丈夫だった)

初めての人がつまづきそうなところは、まとめたほうがいいかもなー。

残りの宿題

  • コードを github にあげる
  • akephalos を使ってみる
  • SeleniumIDE の formatter として cucumber(capybara) を作る
    • アル程度、定型にしちゃえば何とかできるじゃないのかな? と思ったり。これないと不便でしょうがない。

redmine のテストを Selenium2.0 WebDriver を使って firefox と IE の2つのブラウザでやってみる

概要

某ベンダ系アジャイルコミュニティの合宿で Selenium を使ったテストをやってみたので、
復習がてら Windows XP SP3上でタイトルのことをやってみたりする。

テストの内容
  • ログイン
  • チケット作成
  • チケット一覧で作成したチケットを確認
  • ログアウト

まで。

構成

redmine <--> firefox(IE) <--> selenium(webdriver) <--> test::unit
こんな感じ?
selenium と webdriver のところが怪しい。

なぜ Selenium(WebDriver) なの?

なんで WebDriver なの?という説明が Selenium 2.0 と WebDriver — Selenium 日本語ドキュメント にあった。

  • Mult-browser testing including improved functionality for browsers not well-supported by Selenium-1.0.
  • Handling multiple frames, multiple browser windows, popups, and alerts.
  • Page navigation.
  • Drag-and-drop.
  • AJAX-based UI elements.
  • 複数のブラウザを使ったテスト
  • ドラッグ&ドロップのテスト
  • AJAX 使ったところのテスト
  • 複数のフレーム、ウィンドウ、ポップアップ、アラートのテスト
  • Page navigation.(ってふつーにできそうだけど何かあるのかな?)

準備物

Redmine 陣営

各自、適当にインストールする。
selenium-webdriver と cucumber を 1.8.7 に同居させられなかったので Selenium 陣営は 1.9.2 を選択…。

プロジェクト "Walpurgis Night" と ユーザ "qb" をあらかじめ作っておく。
諸般の事情により、とりあえず英語。

操作を記録して、Test::Unit ファイルに吐き出す

firefox -> ツール -> Selenium IDE を選択。
以下の操作を行う。赤い●のボタンを押すと記録開始、も一回押すと記録終了。

  • ログイン
  • チケット作成
  • チケット一覧を表示
  • ログアウト
記録した操作を実行して動くか確かめる

メニュー -> アクション -> 現在のテストケースを実行。

念のため、記録した操作を保存する

メニュー -> テストケースを保存。

記録した操作を Test::Unit に吐き出す

これは便利だなぁ!
メニュー -> テストケースをエクスポート -> Ruby Test::Unit(WebDriver)
ここで、 テストスイート にしちゃうとエラーが出るので注意。

Test::Unit を使って保存した操作を再現する

1.9のおまじないと、rubygems をインポートする
# -*- encoding: utf-8 -*-
require "rubygems"
タイムアウト?の時間を2秒にする
  @driver.manage.timeouts.implicit_wait = 2
URLを変更する(何のためって言えばいいのだろう)
  @driver.get "http://127.0.0.1:3000"
Option を選択するところは自動出力してくれないので自力で書く

select - How do I set a an option as selected using selenium-webdriver (selenium 2.0) client in ruby - Stack Overflow を参考に。
メソッドに抜き出しておいてもよさそう。

実行する

無事チケットができてたらOK

ruby \path\to\testcase.rb

結果を検証する

ちゃんと希望通りの内容になっているのか検証をする。
手抜きをして、

  • チケット一覧で目的のタイトルのチケットがあること
    • 該当するチケットのトラッカー、優先度、担当者が期待通りであること
    • チケット番号は動的に変わってしまうからチェックなし

のみ検証してみる。

検証するために必要な操作
  • チケット一覧(テーブル)を見つける
  • 見つけたテーブルの一番上にあるデータを取り出す
  • タイトル、トラッカー、優先度、担当者の値を取り出し、チェックする

ができないといけない。ふぅ。
ここで Web Developer の出番な訳ですよ!

Web Driver を使って要素の情報を取り出す

使い方は(後で書くかも)
情報 -> 要素の情報を表示する が使えると思う。

テーブルを見つけて値を取り出し、検証する

チケット一覧テーブルの一番上にある行を取り出せば良い感じ。
トラッカーの内容が Feature であることを確認するにはこんな感じ。

  @driver.find_element(:xpath, "//table[@class='list issues']/tbody/tr[1]")
  assert_equal "Feature", latest_issue.find_element(:class, "tracker").text, "tracker"
実行して確かめる
> ruby \path\to\testcase.rb
Loaded suite Redmine-WalpurgisNight
Started
.
Finished in 16.453125 seconds.

1 tests, 6 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 31656

できた!!

firefox だけじゃなくって IE でも動かしてみる

これができてこそ。setup の最初の行をちょいちょいと書き換える

@driver = Selenium::WebDriver.for :ie

ログインする時にパスワードをうんぬんのダイアログが出て最初は失敗したけど、
2回目は成功。
すげーすげー。

でも、いちいちここ切り替えるの面倒。
上手い指定の方法が思いつかないので、環境変数使うことにする。
上手く切り替わったゾ!

ここまでのコード

今度は Cucumber と組み合わせてできるかやってみよう。

# -*- encoding: utf-8 -*-
require "rubygems"
require "selenium-webdriver"
require "test/unit"

class RedmineWalpurgisNight < Test::Unit::TestCase

  def setup
    raise "Please set environment variable 'WEBDRIVER_TYPE' (ex. firefox, ie)" unless ENV["webdriver_type"]
    @driver = Selenium::WebDriver.for ENV["webdriver_type"].to_sym
    @driver.manage.timeouts.implicit_wait = 2
    @verification_errors = []
  end
  
  def teardown
    return unless @driver
    @driver.quit
    assert_equal [], @verification_errors
  end
  
  def test_redmine_walpurgis_night
    @driver.get "http://127.0.0.1:3000"
    @driver.find_element(:link, "ログイン").click
    @driver.find_element(:id, "username").clear
    @driver.find_element(:id, "username").send_keys "qb"
    @driver.find_element(:id, "password").clear
    @driver.find_element(:id, "password").send_keys "homuhomu"
    @driver.find_element(:name, "login").click
	select_option @driver.find_element(:css,'select'), "Walpurgis Night"
    @driver.find_element(:link, "New issue").click
	select_option @driver.find_element(:id,'issue_tracker_id'), "Feature"
    @driver.find_element(:id, "issue_subject").clear
    @driver.find_element(:id, "issue_subject").send_keys "As Incubator, I want magical girl."
    @driver.find_element(:id, "issue_description").clear
    @driver.find_element(:id, "issue_description").send_keys "foobaa"
	select_option @driver.find_element(:id,'issue_priority_id'), "High"
	select_option @driver.find_element(:id,'issue_assigned_to_id'), "qb incubator"
    @driver.find_element(:name, "commit").click
    @driver.find_element(:link, "Issues").click

	latest_issue = @driver.find_element(:xpath, "//table[@class='list issues']/tbody/tr[1]")
	assert_equal "Feature", latest_issue.find_element(:class, "tracker").text, "tracker"
	assert_equal "New", latest_issue.find_element(:class, "status").text, "status"
	assert_equal "High", latest_issue.find_element(:class, "priority").text, "priority"
	assert_equal "As Incubator, I want magical girl.", latest_issue.find_element(:class, "subject").text, "subject"
	assert_equal "qb incubator", latest_issue.find_element(:class, "assigned_to").text, "assignee"
	
    @driver.find_element(:link, "Sign out").click
  end
  
  def element_present?(how, what)
    @driver.find_element(how, what)
    true
  rescue Selenium::WebDriver::Error::NoSuchElementError
    false
  end
  
  def select_option(my_select, option_text)
	my_select.find_elements( :tag_name => "option" ).find do |option|
      option.text == option_text
    end.click
  end
  
  def verify(&blk)
    yield
  rescue Test::Unit::AssertionFailedError => ex
    @verification_errors << ex
  end
end

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 の内容と違っていると...

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

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

本人が行き当たり場当たりな性格なので、予告なく内容が変わることがあります(特にコード)

動機

  • Swing のテストをやることになった
  • どうせなら自動化
  • Cucumber 使えないかな
    • JRuby を使えばいけるかな?
    • UI はどうやって叩くんだろう
      • Cucumber + Swinger でできるらしい
      • Swinger 機能がちょっと少ない?
      • 他にないかな
      • Fest ってのがあるらしいぞ
  • Fest + JRuby + Cucumber + Ant でCIだ

材料

インストール

JDK/Juby/Ant インストール

適当で。

Cucumber インストール
> jgem install cucumber
Fest インストール
  • fest-assert-1.4.jar
  • fest-mocks-1.0.jar
  • fest-swing-1.2.jar
  • fest-util-1.1.2.jar
  • fest-util-1.1.2.jar

を適当なディレクトリに置く。

Step01 - 最初の一歩

ディレクトリを掘る
automated-ui-testing-java
  + src
      + local
         + myproject
             + ticket_viewer
  + test-feature
      + step_definitions
      + support
テストコードを書く
# language: ja
フィーチャ: チケット画面を出すところまで
  シナリオ: No1. プロジェクト一覧を取得する
    前提   "メイン画面" が表示されていること
    もし   "URL" に "http://www.r-labs.org/projects.xml" を入力する
    かつ   "ctlFetchProjectList" ボタンをクリックする
    ならば "プロジェクト一覧" に以下が表示されていること:
      |r-labs        |
      |Hudson        |
      |IssueExtension|
      |WikiExtension |
おもむろにテストを実行する

Windowsのコンソールに出すと文字化けするので、仕方なく HTML に出力する
結果は cucumber.html に出力される。

> cucumber test-feature --format html -out cucumber.html
> cucumber.html

当然のごとくテストは動かないので、ここからイロイロやるわけですな。

Step02 - まずは画面を表示する

製品コードを書く

メイン画面とチケット一覧画面を作る。
適当にコントロールとボタンを配置する。

automated-ui-testing-java
  + src
      + local
         + myproject
             + ticket_viewer
                 + MainFrame.java
                 + TicketListFrame.java 
fest を使って画面を表示する

fest FrameFixture を作って画面を表示する。
別にここでテストするわけじゃないので、単に Frame を setVisible すればイイだけではあるが。

テストシナリオの中で起動しても良いんだけども、at_exit フックを使いたかったので
テストシナリオの前に画面を表示する方向で。
support/env.rb に書けば、事前に実行してくれるんじゃないかな?

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

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

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

#=======================================================================================
# Cucumber のテストが終わった後にやること
#=======================================================================================
at_exit do
  # fest が使った資源をきれいさっぱり掃除する
  mainFrame.cleanUp
end
実行する

画面が一瞬表示されて、終了したらOK。
何もしてないので、当然テストは成功しない。

Cobertura に日本語パッチを当てる 1.9.4.1 版

タイトルは coberturaに日本語パッチをあてる - お仕事の備忘録みたいなもの からいただきました。どもすみません。

※ cobertura のテストが幾つか失敗してます。参考程度にどうぞ。
Cobertura が用意しているテストはクリアしたので、まぁまぁ大丈夫だと思います。が、動作の保証まではできません。ごめんなさい。

  • UTF-8 で "(" を含んだ文字列

が入っていると、ant の cobertura-report タスクで以下のようなエラーを吐いて死ぬ。超困る。何とかせねば。

TokenMgrError: Lexical error at line 17, column 38.  Encountered: "\r" (13), after : "\"\u8b41\uff70\u7e3a\u52b1\uff1e\ufffd\ufffd.equals(arg) ) {"
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParserTokenManager.getNextToken(JavaParserTokenManager.java:2078)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_scan_token(JavaParser.java:10181)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_3R_198(JavaParser.java:8524)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_3R_178(JavaParser.java:8924)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_3R_151(JavaParser.java:8901)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_3R_102(JavaParser.java:8960)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_3_25(JavaParser.java:9977)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.jj_2_25(JavaParser.java:5999)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.Expression(JavaParser.java:2762)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.IfStatement(JavaParser.java:4251)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.Statement(JavaParser.java:3816)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.BlockStatement(JavaParser.java:3997)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.Block(JavaParser.java:3947)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.MethodDeclaration(JavaParser.java:2039)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.ClassBodyDeclaration(JavaParser.java:1082)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.ClassBody(JavaParser.java:941)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.UnmodifiedClassDeclaration(JavaParser.java:854)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.ClassDeclaration(JavaParser.java:761)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.TypeDeclaration(JavaParser.java:608)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.CompilationUnit(JavaParser.java:353)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.parser.JavaParser.parse(JavaParser.java:137)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.Javancss._measureSource(Javancss.java:256)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.Javancss._measureRoot(Javancss.java:339)
[cobertura-report] 	at net.sourceforge.cobertura.javancss.Javancss.<init>(Javancss.java:419)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.ComplexityCalculator.getAccumlatedCCNForSource(ComplexityCalculator.java:104)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.ComplexityCalculator.getAccumlatedCCNForSingleFile(ComplexityCalculator.java:141)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.ComplexityCalculator.getCCNForSourceFileNameInternal(ComplexityCalculator.java:226)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.ComplexityCalculator.getCCNForPackageInternal(ComplexityCalculator.java:196)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.ComplexityCalculator.getCCNForProject(ComplexityCalculator.java:166)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.html.HTMLReport.generateTableRowForTotal(HTMLReport.java:704)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.html.HTMLReport.generateOverview(HTMLReport.java:336)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.html.HTMLReport.generateOverviews(HTMLReport.java:271)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.html.HTMLReport.<init>(HTMLReport.java:96)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.Main.parseArguments(Main.java:107)
[cobertura-report] 	at net.sourceforge.cobertura.reporting.Main.main(Main.java:176)
[cobertura-report] WARN   getAccumlatedCCNForSource, JavaNCSS got an error while parsing the java file D:\Development\_Projects\cobertura-test\.\src\local\my\project\HelloWorld.java
[cobertura-report] TokenMgrError in STDIN
[cobertura-report] Lexical error at line 17, column 38.  Encountered: "\r" (13), after : "\"\u8b41\uff70\u7e3a\u52b1\uff1e\ufffd\ufffd.equals(arg) ) {"

調査

Cobertura をビルドできるようにする
> ant jar

で JAR作成してくれる。

やばそうなところを見つける

スタックトレースから追いかけるだけの簡単なお仕事。
最初は net.sourceforge.cobertura.javancss.parser.JavaParserTokenManager.getNextToken
辺りを見ていたんだけど、さっぱり分からないので、呼び出し元を見ながら分かりそうなところまで行ってみる。

で、分かったこと。

            private Complexity getAccumlatedCCNForSource(String sourceFileName,
                    Source source) {
                if (source == null) {
                    return ZERO_COMPLEXITY;
                }
                if (!sourceFileName.endsWith(".java")) {
                    return ZERO_COMPLEXITY;
                }
                Javancss javancss = new Javancss(source.getInputStream()); /* ここだ! */

コードを修正する

Ant の cobertura-report タスクにある encoding プロパティ?を使えるように修正してみる。

net.sourceforge.cobertura.reporting.Main.java
103:   ComplexityCalculator complexity = new ComplexityCalculator(finder);
104:    complexity.setEncoding(encoding);
net.sourceforge.cobertura.reporting.ComplexityCalculator
 67:    private Map packageCNNCache = new HashMap();
 68:    
 69:    private String encoding;
 95:    private Complexity getAccumlatedCCNForSource(String sourceFileName, Source source) {
 96:    if (source == null)
 97:    {
 98:        return ZERO_COMPLEXITY;
 99:    }
100:    if (!sourceFileName.endsWith(".java"))
101:    {
102:        return ZERO_COMPLEXITY;
103:    }
104:    Javancss javancss = new Javancss(source.getInputStream(), encoding);
245:    public void setEncoding(String encoding) {
246:        this.encoding = encoding;
247:    }
net.sourceforge.cobertura.javancss.Javancss
413:    public Javancss(InputStream isJavaSource_, String encoding) {
414:        Util.debug( "Javancss.(InputStream).sJavaSourceFile_: " + isJavaSource_ );
415:        _sErrorMessage = null;
416:        _vJavaSourceFiles = null;
417:        this.encoding = encoding;

ビルドして実験

無事カバレッジが作成された!!
気の迷いかもしれないので、ソースを元に戻して再度挑戦。大丈夫そう。

テストを通す

せっかく作ってくれているテストは通しておきたい…。

> ant

net.sourceforge.cobertura.test.SwitchFunctionalTest のテストが1つこけるけども、これは修正前もこけてたのでよしとする。

Agile2011 でやってみること

きいてみる

やってる人に聞いてみたいことをまとめてみる。
でも、この質問、英語にしなきゃいけないし、回答も英語なんだよな…。

  • スクラムやってる?
  • Done の定義 Story/Sprint/Release のように分けて決めてる?
    • Story と Sprint 分けてる理由って?
    • Sprint の Done が達成できなかったらどうしてる?
  • ScrumAlliance の Doneの定義書いた人に決めた経緯とか聞きたい
    • あれをベースにこれって何故こうしてるんだと思う?って話をしてみたい
    • 英語力ないとまず無理だな…orz
  • スプリント0 で何してる? 期間はどのくらい?
    • なぜそうしたの?
  • P.O. は受け入れテストどうしてる?
    • 自動化はやっぱりチームが手伝ってる?
  • ドキュメントどれくらい書いてる?
    • ReleaseSprint でドキュメント清書したりしてる?
    • 清書するとしたら、Sprint でどれくらい書いてる?
  • P.O. はどうやって要件を管理してるの?
    • 全てを PBL で管理してる? それとも製品の要件は別文書として管理してる?

最終的には自分で決めなきゃいけないことは分かってるんだけど
やっぱり聞いてみたいのよね。

OpenJam でカンバンでも作って座ってたら、あっちから来てくれるかな?
そしたら話しかける勇気なくても大丈夫…。

  • @sandayuu さんのオブ脳セッションに参加する(手伝いできればいいけど)
  • AgileSamurai を買ってたら作者さんにお礼をいう(今やっていることのヒントになるはずだから)

AgileScout - 大規模開発にアジャイルを適用する際の10のTIPS

10 Tips for Agile Adoption in the Enterprise | Agile Scout より。

英語力は地を這っている人なので、中身についてはあまり信用しないように。

1. マネジメント層を取り込め

彼らを引き込まないと、ウォーターフォール向きのレポートに変換する作業がついて回る。

2. 全リリースの計画をしろ、1スプリントだけじゃない

ストーリーポイントで見積もり、ベロシティを予測し、スプリント計画を立てよう。
でも、最初に立てた計画にガチガチに縛られないよう注意。

3. スプリントを計画しろ、特別、共有なリソースのことを念頭において

遠い未来の計画に縛られる必要はないけれど、計画は立てよう。

4. 複雑な内部の依存関係は現実に起こる、それに対処せよ

リリース計画でモジュールやコンポーネントの依存関係を特定しておこう。スプリントゼロってやつ。
スプリントゼロでは全般的なアーキテクチャの要件?を設計しておく。スプリントのオーバーヘッドとならないように。

5. 妥当なスプリントの長さを継続せよ、3〜4週間

本格的なシステム統合をするには2週間では短い。
3〜4週間の長さを継続する。完全なサービスか、統合を開発し、テストできるように。

6. すべてのスプリントの成果物が製品になるとは思うな

初期のスプリントではEnd-to-Endでのシステム統合を準備することになる。

7. DONEを定義せよ、全てのチームにおいて首尾一貫しているDONEだ

8. 少なくとも2スプリントを安定化のために用意せよ

バグ出しや、システムテスト、デプロイのためのタスクの片付けとか。

9. 使い物になるドキュメントを作れ

大量のドキュメントの代わりに、Agile Document Templete や Wiki を使え。
Agile Documente Template って何?
コメントだけで大丈夫とか言って、ドキュメントをさぼらないように。

10. 継続的インテグレーションの原則を導入せよ

言うまでもなし。


コメントも結構ある。でも訳すの大変だからやらない。