Ruby: Steak + CapybaraでUser-Agentを設定してテストを行う

思いっきり@kazuhisa1976さんの記事のタイトルをパクっていますが、内容もほぼパクリです(ぇ

[絶対R領域] Cucumber + CapybaraでUserAgentを設定してテストを行う

私が使っている受け入れテストフレームワークはSteakなんですが、個人プロジェクトで初めて使いだしたので正直まだ全然わかっていません。SteakはRspecみたいに受け入れテストが書けるので、プログラマがガリガリとテストを書くにはわかり易いし軽いしいいんじゃないか?ということで仕事でも使うかもしれないので調査がてら採用しているところ。

で、自分が作っているアプリはWebアプリなんですが、ターゲットを最初っからモバイルに限定しています。そして、何の気なしにテストコードを流したらViewがないというエラーで落ちました。そりゃそうだ。PC用のViewを準備してないもの。

そこで、モバイル用のViewを見させるにはどうすりゃいいんだろう?と悩んだのですが、CapybaraのUser-Agentを偽装するんだろうなーと思ってググったら、某R社のスーパーアーキテクトのかずさんブログがヒットしたわけです。すごい。1番上に出てきましたよ!!

SteakでUser-Agentを偽装するのも全く同じ方法でできます。
spec/acceptance/support/helpers.rbの先頭に以下を追加しておきます。

# HTC EVOのUser-Agent
Capybara.register_driver :android do |app|
  Capybara::RackTest::Driver.new(app,
  :headers => {'HTTP_USER_AGENT' => 'Mozilla/5.0 (Linux; U; Android 2.2.1; ja-jp; ISW11HT Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1'})
end

同様に、setup_useragentメソッドを作っておきました。新たにUser-Agentを追加した場合はここのcase文をいじるなりすればいいのかなと。

module HelperMethods
  def setup_useragent(target)
    case target
    when :android
      Capybara.current_driver = :android
    else
    end
  end
end

で、Steakのコードはこんな感じ。シナリオの先頭でUser-Agentの切替を行ってから、テストを実行しています。

# coding: utf-8
require File.expand_path(File.dirname(__FILE__) + '/acceptance_helper')

feature "サンプルテスト", %q{
  サンプルテストを記述する。
  なぜなら自分が後から見てもわかるようにするからだ。
} do
  background do
    user = FactoryGirl.create(:user)
    3.times { FactoryGirl.create(:list, :user => user)
  end

  scenario "自分の作成したリストを表示する" do
    setup_useragent(:android)
    do_login # HelperMethodsで定義してると仮定
    visit own_list_page
    page.should have_content("リスト1")
    page.should have_content("リスト2")
    page.should have_content("リスト3")
  end
end

Cucumberになれているとちょっと書きにくく感じてしまうのですが、プログラマの感覚で書いていくと直感的に書き易くていいんじゃないかと思いますよ、Steak。
ひとまずこれでモバイルページも怖くない!!(はず)


岡山Git勉強会に参加・発表してきた

6/16(土)に、すますん(@ryosms)主催のgit勉強会が行われました。参加者が30人越えという結構でかい勉強会になりました。

ATND: 岡山git勉強会201206

流れは、発表3人とgitハンズオンをみんなでやっていこうという感じ。

発表者と内容は

というもの。

すますんの発表は、いかにしてgitを会社に導入させたかという話。なぜgitなのか、反対勢力を説得するためにどうしたか、というもの。

会社にもソース管理への危機意識はあったから、わからない人へのサポートと、まずは新人から仕込んでいくという感じがまぁいいだろうと。あと、既にソース管理ツール使ってる場合は無理にgitを入れる必要はない。プロジェクトの最初から入れられるようなら、入れるべきという感じ。

すますんのスライドのURL:
http://prezi.com/rx8zxzvw1wfm/git/

次はゼフィさんのGithubの話。
GithubはSNSだぜ!!という一言に集約できるが、コードで語り合うSNSだと。
基本的なGithubの機能説明と、Gitとは違うGithubのソーシャル部分の説明(pull requestとfork)。forkして自分で直してpull requestしてコントリビュートするというのは、俺はまだやったことないのでなんかのオープンソースプロジェクトでやってみたいなぁと思った。

以下、ゼフィさんのスライド。

最後は自分。
Successful gitを試してみたが…という題名で、自社でやっているgit運営の事例紹介みたいなものを紹介。そして、うまくいかない部分があるんだけどどーすればいいと思う?という質問をしてみた。

以下、俺のスライド。

むおおおお(@esperia09)からgitflowを使ってみてはどうか?という意見をもらったりした。
あとは、リリース先ごとにブランチを切って、都度マージしていくという意見も。でもそうすると管理が大変になっていきそうな気がするんだけどなー…。
資料を作りながら自分が思ったのは、リリースバージョンタグとは別に社内用のバージョンタグを作って、それで管理していくというのはどうか?と思った。

その後の残った時間で、gitのハンズオン。
Git Immersionというサイトがあり、そこの課題をこなしていくとgitのイロハがわかるようになる(ただし英語)。
これをみんなでやっていこうということで、すますんが説明しながら課題12まで終わらせて終了した。

ここの課題自体は難しくないし、やっていることもコードを見ればだいたいわかる(と思う)ので、後でまた個人か会社のメンバーでやってみたらいいかなと思った。

たまさんがとぅぎゃってくれてたのでそれにもリンクはっときます。
毎度毎度とぅぎゃってくれてありがとうございます!

岡山git勉強会 201206 まとめ


Ruboto: 非同期処理を行う(Thread, Handler)

RubotoでAsyncTaskを使おうとしてみたのだが、AsyncTaskを継承したクラスを作って使おうとしたらArgumentErrorになってしまい、試行錯誤したのですがよくわからなかったので、ThreadとHandlerを使って非同期処理を行うようにしてみた。

しかしこれもまた結構ハマった…。現状、非同期で動いたコードを載せておく。

import "android.os.Handler"
import "java.lang.Runnable"
import "java.lang.Thread"
.
.
.
# 何かしらの処理の呼出しなど
def test
  @dialog.show # ProgressDialogを事前に作成しておく
  handler = android.os.Handler.new
  runnable = java.lang.Runnable.impl do
    # 通信処理などを行う
    handler.post(proc{
      #ここで描画処理
      @dialog.dismiss
    })
  end
  java.lang.Thread.new(nil, runnable, "large_stack", 64 * 1024).start
end

この書き方ならうまくいったのだが、最後のjava.lang.Thread.new(nil,runnable, …)のやつは、ruboto/util/stack.rbのThread.with_large_stackメソッドと同じ書き方をしている。なので、Thread.with_large_stack{ runnableに定義している処理 } でもうまく処理できるんじゃないか?と思ってそうしてみたが、エラーで落ちてしまった。

Rubyの無名関数の辺りのことはまだよくわかっていないので、ちょっとひとまずここまでとする。handler.send_messageからメッセージを送ってhandler.handle_messageで受け取って処理をしようとしたのだが、これもまた一切メッセージが届かなかった(試していた時からソースが変わったからやり方間違えていたかも)。handler.post方式にしたらうまくいった。

まさにヨチヨチ歩きのスピードであるが、とりあえず謎が少し解けてよかった。
AsyncTaskでやる方法とかもちゃんと知りたいなぁ。


Ruboto: スレッド処理の方法

RubotoでRSSを読み込もうと思ってrequre “rss”をしたらパーサーがおかしいといわれてアプリが落ちてしまった。

代替案として、Rubygemsからsimple-rssというgemをとってきた。
これはエラーにならなかったのだが、stack too largeというよくわからないエラーが出てしまってもうわけがわからなくなった。非同期で処理をするしかデータを取る方法がないのだろうか?(しかしまだRubotoで非同期処理する方法が俺はわかってない。AsyncTaskを継承したらエラーになったし。継承の仕方が悪いのかもしれんが)

そう思っていて、まぁとりあえずAndroidアプリを作る時に必要になるメニュー類の出し方とか先に調べとこうと前回の記事を書いていたのだが、RubotoはRuboto irbのScriptにあるソースを読むのがすごい勉強になりそうだったのでXOOMで見ていたら、JavaのHTTPClientを使って通信しているっぽいのを見つけた。これで通信できるんじゃねーの!?と思ってコードを追いかけた結果、with_large_stack &block というメソッドで囲まれていた(通信部分がブロックで)。

先ほどのsimple-rssでRSSを取得するところがstack too largeで落ちていたのだが、with_large_stackを使えば、そうならないのではないか?と考えた。
結論からいうと、うまくいった。
使い方はこんな感じ。

require "ruboto/util/stack"
.
.
.
# 通信部分をwith_large_stackで囲んでみた。
rss = SimpleRSS.parse with_large_stack{ open('http://slashdot.org/index.rdf') }

with_large_stackメソッドは、src/ruboto/util/stack.rbにある。
stack.rbはそこまで長くないコードなので、載せておく。ちなみにRubotoのバージョンは0.6.0です。

#######################################################
#
# ruboto/util/stack.rb
#
# Utility methods for running code in a separate 
# thread with a larger stack.
#
#######################################################

class Object
  def with_large_stack(opts = {}, &block)
    opts = {:size => opts} if opts.is_a? Integer
    opts = {:name => 'Block with large stack'}.update(opts)
    exception = nil
    result = nil
    t = Thread.with_large_stack(opts, &proc{result = block.call rescue exception = $!})
    t.join
    raise exception if exception
    result
  end
end

class Thread
  def self.with_large_stack(opts = {}, &block)
    opts = {:size => opts} if opts.is_a? Integer
    stack_size_kb = opts.delete(:size) || 64
    name = opts.delete(:name) || "Thread with large stack"
    raise "Unknown option(s): #{opts.inspect}" unless opts.empty?
    t = java.lang.Thread.new(nil, block, name, stack_size_kb * 1024)
    t.start
    t
  end
end

Objectクラスを拡張してどこからでもwith_large_stackを呼べるようにして、これに渡ったブロックはJavaのThreadクラスでうまいこと処理してくれて、例外がなければ戻り値を返してくれるということのようである。

ブロック内でプログレスダイアログの呼出しなども行えば、AsyncTask的なのがすぐにできそうな気がする。
使うときは、

require "ruboto/util/stack"

を忘れないように。


Ruboto: メニューを設定する

Rubotoでメニューを設定するメソッドは、src/ruboto/menu.rbに定義されています。

handle_create_options_menuメソッドです。
onCreateOptionMenuメソッドに相当するでしょうか。

handle_create_options_menuにはブロックを渡すので、その中でadd_menuメソッドを呼びます。

add_menu(title, icon=nil, &block)

  • title: タイトル
  • icon: アイコン(あれば)
  • &block: メニューが押されたときに実行する処理

それを、$activity.start_ruboto_activity “$main_activity” ブロック内で呼びましょう。
以下、サンプルです。

# coding: utf-8
require 'ruboto/activity'
require 'ruboto/widget'
require 'ruboto/util/toast'
require 'ruboto/menu'

ruboto_import_widgets :LinearLayout, :ListView

$activity.start_ruboto_activity "$sample_activity" do
  setTitle 'This is the Title'
  
  def on_create(bundle)
    @list = ["鮭", "鰤", "鰯", "鯵", "鮪", "鯨"]
    self.content_view =
        linear_layout(:orientation => :vertical) do
          @list_view = list_view(:list => @list,
                                 :on_item_click_listener => proc{|parent, view, position, item_id|
                                   toast(@list[position])
                                 })
        end
  end

  # メニューを定義する
  handle_create_options_menu do |menu|
    add_menu("Hello") { toast "Hello, Ruboto"}
    add_menu("Exit") { finish }
    true
  end
end