«前の日記(2010-08-27 (金)) 最新 次の日記(2010-08-29 (日))»  

まちゅダイアリー


2010-08-28 (土)

tDiary 3.0 時代のテスト方法 (steak/capybara, rack, bundler)

昨日に引き続いて RubyKaigi 2010 に参加した。 今日の(個人的な)メインイベントは、もちろん tDiary 会議。 会議は、 @hsbt さんによる tDiary の git + テスト講座、 tDiary 3.0.0 のリリース、次の10年に向けてのユーザを考えるという流れ。 無事に会場から 3.0.0 がリリースされ、リリースノートも公開された。

このリリースは新しい安定版シリーズである3.0系列の最初のリリースになります。2.2系から3.0への最大の変更点は、「UTF-8化」と「ruby 1.9対応」です。

もちろんこれまでどおりに1.8系でも動くし、 EUC-JP から UTF-8 へは自動で移行されるなど、安心して 3.0.0 へバージョンアップできるようになっている。 このように、普通に使う上ではあんまり変わっていないように見える tDiary だけど、実は開発者の視点ではもっと興味深い技術が盛り込まれようとしている。

それが testable tDiary 。まだメインブランチには取り込まれていないけど、 git で testable ブランチを checkout することで、新しい技術が満載の先鋭的な tDiary が表れる。 まさに、「モード反転。裏コード、ザ・ビースト」のように。

  • Rack 上で tDiary が動く (Rack::TDiaryApp)
  • bundler による依存ライブラリ (RubyGems) の管理
  • rspec によるテストの記述
  • steak / capybara による受け入れテストの記述

これら技術を使っている目的は、 Ruby遺産 tDiary をテスト可能にするため。 @kakutani さんが Rack 対応の Rack::TDiaryApp を作り、 @hsbt さんがその上で steak / capybara を使ってテストケースを書いている。 テストケースは80%のコードをカバーしているとのことだった。 誤解の無いように補足すると、 tDiary をただ使うだけの場合は関係がなく、 rack がないと tDiary が動かない!とかにはならない。 あくまで、開発者がテストを動かすために使うもの。

今日の tDiary 会議で @hsbt さんから説明があったんだけど、時間が限られていることもあって、なかなかついて行くのが大変だった。 rack や rspec はともかく、 bundler や steak / capybara はまだ日本語の情報が少ないこともある。 そこで、お昼休みの asakusa.rb や懇親会で @hsbt さんをつかまえて疑問点を聞いて、自分なりに全体像を把握した。 間違っているところもあるかもしれないけど、いったんまとめてみる。

testable tDiary の構造

コードの前に図から入る性格なので、まずは構造の図を書いた。

testable-tDiary

下から説明すると、まず bundler というツールが、 testable tDiary で使用する関連ライブラリ (RubyGems) の依存関係を管理してくれている。 後述するように RubyGems と bundler だけインストールすれば、あとは bundler が必要な関連ライブラリをインストールしてくれる。 bundler で管理された環境の中で、テストツール (rspec) が動作している。 rspec はテストケースを1つ1つ実行し、結果をコンソールに表示するのが役割。

実際のテストの仕組みは、 rspec 上で動く capybara / steak が提供している。 capybara / steak は、 Rack アプリに対する統合テスト (integration test) を提供するライブラリ。 Web ブラウザを使ってテストするようなレベルのテストを自動化してくれる。 hsbtさんの日記に少しだけ言及がある

後でちゃんと書くけど、自分は上の Rack::TDiaryApp と steak + capybara で受け入れる人が開発者自身という受け入れテストを追加しているので、これがもう少し機能をカバーできるようになってから中身に手を入れるつもり。RubyKaigi2010までには何とか steak で外堀を固めてから、Rack層に手を入れたいなー。

ここ重要。 自動テストというと xUnit や(ふつうの) RSpec のように、 MVC でいうモデルを直接操作してその結果を確認することが多い。 でも、 tDiary の場合は MVC が完全に分離されておらず、これからリファクタリングされることになるため、いきなりモデルのテストは書きづらい。 そこで最初に外側からみた振る舞い(受け入れテスト)を作成し、そのあとにリファクタリングするというアプローチを採っている。 これは正しいと思うな。

そして、テストを自動化するために tDiary の Rack 化が必要だったということ。 (ただし、現状の Rack 化はあくまでテスト環境で動かすことを目的としていて、実環境で使うにはまだまだ修正が必要)

環境構築からテスト実行まで

長くなってきたので、一気に書く。 必要環境は、 ruby と rubygem と git の3つ。 ここでは以下の環境で試した。

$ git --version
git version 1.7.1
$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin10]
$ gem -v
1.3.7

git や RubyGemのインストール方法はググればでてくるので、さすがに省略。

bundler のインストール

まだ bundler をインストールしていない場合は、 gem コマンドを使ってインストールする。 --pre オプションを付けているのは、まだ Bundler 1.0 がリリースされていないから。 1.0がリリースされた後は、 --pre コマンドは不要になる。

$ gem install bundler --pre
$ bundle -v
Bundler version 1.0.0.rc.5
tDiary リポジトリからソースを取得

GitHub の tDiary リポジトリから、最新のソースを取得する。

$ git clone git://github.com/tdiary/tdiary-core.git
Initialized empty Git repository in /Users/machu/work/tdiary-core/.git/
remote: Counting objects: 6907, done.
remote: Compressing objects: 100% (1821/1821), done.
remote: Total 6907 (delta 5064), reused 6712 (delta 4957)
Receiving objects: 100% (6907/6907), 1.38 MiB | 177 KiB/s, done.
Resolving deltas: 100% (5064/5064), done.

$ cd tdiary-core
$ git branch -a
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/testable

この状態では master というブランチ (現時点ではリリースされている3.0.0と同じもの) を開いた状態なので、 testable ブランチへと切り替える。

$ git branch testable origin/testable
Branch testable set up to track remote branch testable from origin.
$ git branch
* master
  testable
$ git checkout testable
Switched to branch 'testable'
$ git branch
  master
* testable

git branch コマンドを実行して、testable の先頭に * が表示されていればOK。 testable ブランチをチェックアウトした状態で ls コマンドを打つと、 master ブランチにはない spec ディレクトリや tdiary.conf.rack, tdiary_app.rb, Gemfile, config.ru, Rakefile などのファイルが出来ていることが分かる。

関連ライブラリのインストール

テストに必要な capybara などの関連ライブラリをインストールする。 といっても、自分で gem コマンドを使って1つずつインストールする必要は無く、 bundler が必要なライブラリを自動でインストールしてくれる。

$ bundle install
Fetching source index for http://rubygems.org/
Installing rake (0.8.7) 
Installing culerity (0.2.10) 
Installing mime-types (1.16) 
Installing nokogiri (1.4.3.1) with native extensions 
Installing rack (1.2.1) 
Installing rack-test (0.5.4) 
Installing ffi (0.6.3) with native extensions 
Installing json_pure (1.4.3) 
Installing rubyzip (0.9.4) 
Installing selenium-webdriver (0.0.27) 
Using capybara (0.3.9) from git://github.com/jnicklas/capybara.git (at master) 
Installing configuration (1.1.0) 
Installing launchy (0.3.7) 
Installing rr (0.10.11) 
Installing rspec (1.3.0) 
Installing steak (0.3.8) 
Using bundler (1.0.0.rc.6) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

ちなみに必要なライブラリは、 Gemfile と Gemfile.lock の中に書かれている。

受け入れテストの実行

テストは spec/acceptance ディレクトリ内に置かれている。 例えば新しい日記を書くテストを定義している append_diary_spec.rb の中身を見てみる。

$ cat spec/acceptance/append_diary_spec.rb
# -*- coding: utf-8 -*-
require File.expand_path('../acceptance_helper', __FILE__)

feature '日記の追記' do
        background do
                setup_tdiary
        end

        scenario '日記のプレビュー' do
                visit '/'
                click_link '追記'
                within('div.day div.form') {
                        within('div.title') { fill_in "title", :with => "tDiaryのテスト" }
                        within('div.textarea') {
                                fill_in "body", :with => <<-BODY

 ! さて、テストである。
 とりあえず自前の環境ではちゃんと動いているが、きっと穴がいっぱいあるに違いない:-P
 BODY
                        }
                }

                click_button 'プレビュー'
                within('div.day span.title'){ page.should have_content "tDiaryのテスト" }
                within('div.day div.section'){
                        within('h3') { page.should have_content "さて、テストである。" }
                        page.should have_content "とりあえず自前の環境ではちゃんと動いているが、きっと穴がいっぱいあるに違いない:-P"
                }
        end

        # 以下省略

このテストでは、「日記の追記」という機能に対して「日記のプレビュー」というシナリオを定義している(他にも、「今日の日記を書く」などのシナリオも存在する)。 トップページ (/) にアクセスして、追記ボタンをクリック。 次に、タイトルとテキストエリアに日記を入力してプレビューボタンをクリック。 プレビュー画面にタイトルと本文が表示されていることを確認している。

テストを実行するには、 bundle exec コマンドの後に spec [specファイルのパス]を指定する。 (bundler が管理しているライブラリが読み込まれた環境でコマンドが実行するために、bundle exec が必要になる)

$ bundle exec spec spec/acceptance/append_diary_spec.rb
.....

Finished in 4.003664 seconds

5 examples, 0 failures

5つのテストを実行して、すべて成功した。

spec コマンドを使って1つずつテストを実行するだけでなく、 rake コマンドを使うことでまとめて実行することもできる。

$ bundle exec rake -T

(in /Users/machu/Sites/tdiary-core)
rake clean              # Remove any temporary products.
rake clobber            # Remove any generated file.
rake coverage           # all coverage
rake coverage:clean     # delete aggregate coverage data
rake docs               # generate rdoc files
rake spec               # Run specs
rake spec:acceptance    # Rub the code examples in spec/acceptance
rake spec:all           # Run the code in spec
rake spec:clobber_rcov  # Remove rcov products for rcov
rake spec:core          # Rub the code examples in spec/core
rake spec:plugin        # Rub the code examples in spec/plugin
rake spec:rcov          # Run specs w/ RCov

$ bundle exec rake spec:acceptance
FF...*.......***FF...***..*F******....

自分でプラグインを書いたときは、これらのファイルを参考にして、同じように振る舞いのテストを書けばいいかな。

まとめ

  • tDiary 3.0.0 がリリースされた
  • testable ブランチで tDiary がテスト可能に!
  • テストを動かすために、最新の技術・ライブラリ (rack, bundle, steak, capybara) が活用されている

ちなみに、今は testable ブランチに分かれているけど、近いうちに master ブランチへ取り込む予定とのこと。 こういう新しい仕組みに興味がある人は、ぜひ見てみるといいと思うよ。