Tag Archives: rspec

RSpec2で特定のテストを実行する

Relishより、次のようにすることで特定のdescribe/context/itのみを実行することができる。

spec_helper.rbのRSpec.configureにfilter_runの設定を追加:

RSpec.configure do |c|
  c.filter_run :focus => true
end

テストしたいdescribe/context/itに:focusオプションを追加:

describe User do
  describe "#say", :focus => true do
    it "should return a String" do
      :
    end
  end
end

こうすることで:focus => trueを指定したテストのみが実行され、それ以外は無視される。複数個指定すればそれらすべてが実行されるし、ひとつも指定しなければ何もテストされない。テストコードが多い中で新規にテストを追加する場合などに便利だと思う。

上記のRelishのページに書かれている run_all_when_everything_filtered を設定すれば、:focusが1つも指定されていない場合に全テストを実行するというもの。


RSpec 1.3でカスタムマッチャ

ある処理で行われるファイルの移動が適切かどうかテストしたかったので、カスタムマッチャを作ってみた。使用したのはRSpec 1.3.1。

目標のspec

lambda { foo }.should move_file(src, dest)

チェックする項目

  1. fooメソッドを実行する前にsrcにファイルが存在することをチェック(事前チェック)
  2. Foo#barを実行した後にsrcにファイルが存在しないことをチェック(事後チェック)
  3. Foo#barを実行した後にdestにファイルが存在することをチェック(事後チェック)
  4. srcとdestのMD5値が一致することをチェック(事後チェック)

(1は要らないかも?)

実際のコード

CustomFileMatchers::MoveFileのmatches?で具体的なテストを実行する。

使い方

describe文のなかでincludeして使う。 ためしにちゃんと判定できているかもspecで書いてみた。 (it文の内容が微妙かもしれない)

まとめ

意外と簡単にできたし、specのコードがコンパクトかつそれだけで意味が通じるようになるのでカスタムマッチャはとても便利。

複数のチェック項目が通って一つの意味を成す場合にとても強力。

change.from.toのように条件の追加をメソッドチェインしたければCustomFileMatchers::MoveFileに引数をインスタンス変数に保存するようなメソッド(changeでいうところのfromやto)を追加して、matches?でそれらをチェックすればいいのだと思われる。

参考資料


watchrでautospecみたいなこと

watchrで更新のあったファイルに対してrspecを実行する方法。

secondlifeさんの記事を読んで、取り急ぎやってみた。

$ gem install watchr
$ gem install ruby-fsevent # Mac OSXの場合
$ gem install rev # Linux/*BSDの場合
$ cd (RAILSアプリのルートディレクトリ)
$ vi spec.watchr
watch('app/(.*)\.rb') { |md| system("ruby script/spec spec/#{md[1]}_spec.rb") }
watch('spec/(.*)_spec\.rb') { |md| system("ruby script/spec #{md[0]}") }
$ watchr spec.watchr

autospecより汎用性があって便利かも。

追記

Rails3だとbundle exec rspecを使うのでこんな感じ?


watch('app/(.*)\.rb') { |md| system("bundle exec rspec spec/#{md[1]}_spec.rb") }
watch('(lib/.*)\.rb') { |md| system("bundle exec rspec spec/#{md[1]}_spec.rb") }
watch('(spec/.*)_spec\.rb') { |md| system("bundle exec rspec #{md[0]}") }

Rails勉強会@東京第58回に行ってきた

昨年12/18に行われたRails勉強会@東京第58回に行ってきた。

テストの話

「テストの話」で大まかに扱われるけど、細かく見ると次の3つの話題が出てくる。

  • どうやってテストを書くか
  • うまいテストの仕方・資産としての残し方
  • テストの文化の浸透のさせ方・継続の仕方

今回は@moroさんをファシリテータとして、「rspecでテストをどう書くか」と「RelishでRspecの新機能を見る」の2点を行った。

話題がバラバラになりがちなので、進行をされた@moroさんはすごいなと思っていたんだけど、ここらへんをうまくカバーできるやり方を見つけたいと思う今日この頃。

で、その時の結論をうけた上で今覚えてる限りの結論。

どうやってテストを書くか

(いつか書く)

うまいテストの仕方・資産としての残し方

今回は出なかったけど、捨てるテスト、残すテストの話、かなぁ。

開発時の内部実装まで含めた、細かい挙動確認のためのテストで残しておくと後々リファクタリングすることができなくなるので、開発が一段落したらいっそ捨てるのもアリ。

それに対して外側からアプリケーションを叩いてそのINとOUTを見るためのテストは資産として残さなきゃいけない。

テストの文化の浸透のさせ方・継続の仕方

Hudsonを使って継続的にテストの実行とRCovによるカバレッジの集計をさせてそれを共有するのが良いらしい。Rubyではないけどサイボウズでの事例も興味深い。

あとはペアプロでTDDに慣らしていくとか。

あとこれはちょっと別かもしれないけど、「最初にテストを書かなきゃいけないという話だけど、なかなか難しい」という話が出てきた。それに対して@moroさんの話では、「最終的にテストを書いてチェックができればいいので、実装とテストのどちらが先になっても問題はない」ということだった。

自分の場合も、動作の確認がちゃんとできればいいやと思って実装してからテストを書くことは多い。もちろん実装の規模にもよるんだろうけど。

OmniAuthの話

(あとで書く)

ActiveSupport 3系のソースを読む

(あとで書く)


Rails3でRSPec2とmachinist2

Rails3でRSpec2machinist2を使うための下準備。

$ alias r=rails
$ r new testapp -T -d mysql
$ cd testapp
$ vi config/application.rb # Test::Unitにさよなら
$ vi Gemfile # RSpec2とmachinist2を追加
$ bundle install
$ r g rspec:install
$ r g machinist:install
$ vi config/application.rb # fixtureの代わりにblueprintを追加するように
$ vi ~/.autotest # DBを毎回クリアする

各種ファイルの編集は下記の通り。

Test::Unitにさよなら

-require 'rails/all'
+require "active_record/railtie"
+require "action_controller/railtie"
+require "action_mailer/railtie"
+require "active_resource/railtie"

RSpec2とmachinist2を追加

RSpec2+Rails3+autotest環境の構築あたりを参考にやってみた。でもautotestが上手く動かない気がする。

source 'http://rubygems.org'

gem 'rails', '3.0.3'
gem 'mysql2'

group :development, :test do
  gem 'rspec-rails'
  gem 'autotest'
  gem 'webrat'
  gem 'faker'
  gem 'machinist', '>= 2.0.0.beta1'
end

fixtureの代わりにblueprintを追加するように

以下をconfig/application.rbのclass TestApp::Application内に記述すると、モデルを追加したときにfixtureではなくblueprintの追加をしてくれる。

config.generators do |g|
  g.fixture_replacement :machinist
end

DBを毎回クリアする

~/.autotestに下記を記述してDBを毎回クリアする。じゃないとコケる。 「Rails3 + rspec2 その他もろもろ環境でのautotest周りの設定とか」を参照。

# オートテスト開始時にテストDBをクリアする
Autotest.add_hook :initialize do |at|
    system("rake db:test:prepare")
    next false
end

# ファイル更新時にテストDBをクリアする
Autotest.add_hook :ran_command do |at|
    system("rake db:test:prepare")
    next false
end

Rails3でTest::Unitを無効化

Rails3でTest::Unitを使わないようにする方法。rspecの導入方法については参考資料を参照。

まずrails newコマンドで新規プロジェクトを作成する際に-Tオプションを付けて、testディレクトリを作成させないようになる。

$ rails new new_project -T

次にconfig/application.rbを次のように編集して、rails/test_unit/railtieを読み込まないようにする。こうすることでTest::Unitのテンプレート自動生成等を行わないようにする。

--- config/application.rb.org  2010-10-05 11:39:47.000000000 +0900
+++ config/application.rb   2010-10-05 11:41:59.000000000 +0900
@@ -1,6 +1,9 @@
 require File.expand_path('../boot', __FILE__)
 
-require 'rails/all'
+require 'active_record/railtie'
+require 'action_controller/railtie'
+require 'action_mailer/railtie'
+require 'active_resource/railtie'
 
 # If you have a Gemfile, require the gems listed there, including any gems
 # you've limited to :test, :development, or :production.

参考資料


Machinist+DatasetでFixture代替

概要

Machinistは定義された条件下でテストデータを生成するプラグイン・gem。 DatasetはRubyのコードで記述したテストデータをDBに読み込むプラグイン・gem。

この2つを組み合わせることで(比較的)メンテナンスのしやすいfixtureの代替を構築することが可能になる。 流れとしては、Dataset上でMachinistを呼び出して複数のデータを生成して、それをDBに流し込むという感じ。

メリットは、

  • 条件下でランダムなデータを生成してくれる(「特定のデータをfixture上に作って読み込む」というようなことも可能)
  • リレーション先のデータも適宜生成してくれる(素敵!)。

デメリットは、

  • データがランダムなので、全条件を必ずカバーするのは苦手&毎回同じデータが入ってくるとは限らない(回避できるかなぁ)
  • fixtureに依存した書き方をしていると大幅な書き換えが必要かもしれない(レコード数によるテストなど。集計系では難しい…)

最終結果のコードをGithubにあげたので、そちらも参考に。

下準備

MachinistDataset、そして適当な文字列のデータを作ってくれるFakerをインストール・設定する。なおテストフレームワークにはRSpec on Railsを用いる。

YAMLに頼らないテストデータの作成を参考にさせていただきました。

Machinist

$ cd RAILS_ROOT
$ ruby script/plugin install git://github.com/notahat/machinist.git
$ vi spec/blueprints.rb   # 新規作成/下記参照
$ vi spec/spec_helper.rb  # 下記参照

RAILS_ROOT/spec/blueprints.rbに追記:

require 'machinist/active_record'
require 'sham'

RAILS_ROOT/spec/spec_helper.rbに追記:

# 冒頭のほうに記述
require File.expand_path(File.dirname(__FILE__) + "/blueprints")

# Spec::Runner.configureブロック内に以下を記述
# -> Shamで1度生成したデータはリスト形式でキャッシュされ、
#    以降はbeforeが呼ばれるたびにその先頭から順に取り出すようになる
config.before(:all)    { Sham.reset(:before_all)  }
config.before(:each)   { Sham.reset(:before_each) }

Dataset

$ cd RAILS_ROOT
$ ruby script/plugin install git://github.com/aiwilliams/dataset.git
$ mkdir spec/datasets
$ vi spec/spec_helper.rb  # 下記参照

RAILS_ROOT/spec/spec_helper.rbに追記

require 'dataset'
class Test::Unit::TestCase
 include Dataset
 datasets_directory "#{RAILS_ROOT}/spec/datasets"
end

Faker

$ sudo gem install faker

Fakerについては取り扱われた記事が複数あり、RDocだけでも十分な気がするので、今回は特に説明を行わない。

Machinistでデータのひな形定義・作成

Machinistではモデルのテストデータのひな形(blueprint)を定義し、そこからテストデータをランダムで生成する。デザインパターン的にはファクトリパターンを活用していることになる。

以下の例に用いるモデルとしてPersonとGroupを考え、Group has_many Personという関係があるとする。

Groupは次のような属性値を持つ(timestampは省略):

カラム名
PK id integer
name string
hidden boolean

Personは次のような属性値を持つ(timestampは省略):

カラム名
PK id integer
name string
FK group_id integer
hidden boolean

テストデータのひな形の定義

MachinistではRAILS_ROOT/spec/blueprint.rbでモデルごとにテストデータのひな形を定義する。

まず、属性値に使われるダミーデータの条件をMachinistに同梱されたShamを使って定義する。その際のフォーマットは次のようになる:

Sham.データ名 { データ生成メソッド }

このデータ名はモデルの属性名と一致する必要は無い(使い回しが可能)。 次に例を示す。

Sham.person_name { Faker::Name.name } # Faker::XXX.xxxは自動的にランダム生成してくれる
Sham.group_name { Faker::Company.catch_phrase }
Sham.age { rand(100) } # 自分でデータを用意するときは、適当にrand関数を使う

# Shamはダミーデータを生成するときに、デフォルトではユニークになるように調整する
# ユニークな必要が無い・それだとデータ数が足りない場合には:uniqueオプションでfalseを設定して重複を許可する
Sham.boolean(:unique => false)    { [true, false].rand }

上記は次のようにまとめて定義することもできる。

Sham.define do
 person_name { Faker::Name.name }            # Group#nameとPerson#nameは似ているけど別物なので
 group_name { Faker::Company.catch_phrase }  # 別個に定義しておく
 age { rand(100) }
 boolean(:unique => false) { [true, false].rand } # boolean型は使い回しが効くので、一般的な名前をつけておく
end

続いて、モデルのテストデータのひな形は次のように定義する。

Group.blueprint do
 name { Sham.group_name } #=> データ生成時にはSham.group_nameで定義したFaker::Company.catch_phraseが呼び出される
 hidden { Sham.boolean }
end

Person.blueprint do
 name { Sham.person_name }
 age #=> Sham中に同じデータ名があればそれが利用される(ここではSham.ageが呼ばれる)
 hidden { Sham.boolean }
 group { Group.make } #=> ここがキモ!(後述)
end

Machinistではモデルにmakeというメソッドが追加されており、このメソッドでblueprintの定義に従ってテストデータを生成・DBに保存する。 そして、Person.blueprint中のgroup { Group.make }部分では次の二つを実行するように指定している:

  • Personのテストデータが生成されるときに、belongs_to先のGroupをGroup.blueprintで定義された通りに自動的に生成・保存してね
  • ついでにそのGroup#idをさっきPerson#group_idに設定してね。

このおかげでbelongs_toが自動的に生成されるため、ちょっとリレーションの設定が楽になる。 ただしhas_one/has_many関係は別途生成する必要がある(きっとあとで書く)。

最終的に次のようなRAILS_ROOT/spec/blueprint.rbになる。

require 'machinist/active_record'
require 'sham'
require 'faker'

Sham.define do
 person_name { Faker::Name.name }
 group_name { Faker::Company.catch_phrase }
 age { rand(100) }
 boolean(:unique => false) { [true, false].rand }
end

Group.blueprint do
 name { Sham.group_name }
 hidden { Sham.boolean }
end

Person.blueprint do
 name { Sham.person_name }
 age
 hidden { Sham.boolean }
 group { Group.make }
end

specファイルから呼び出す

下準備の段階でMachinistに関するファイルは読み込まれるようになっているので、特にspecファイル中でrequireを行う必要は無い。

以下の3つのメソッドを利用してテストデータを生成して利用する:

メソッド名 概要
Person.make Personをblueprintに従って生成し、DBに保存したのちにそのインスタンスを返す(Person.createに相当)
Person.make_unsaved Personをblueprintに従って生成し、DBに保存せずにそのインスタンスを返す(Person.newに相当)
Person.plan Personの属性値をblueprintに従って生成し、それをHashで返す(:idは含まない)

blueprintの定義を一部上書きしてデータを生成したい場合には、次のように各メソッドの引数にHashで指定する:

grand_father = Person.make(:age => 200)

名前付きblueprint

場合によっては、通常の属性値とは異なるパターンの属性値を持ったテストデータを作りたくなることがある。 その場合には、次のようにblueprintの先頭に引数でパターンの名前を付けて別個に定義する。

次の例ではPersonの別blueprintとして:wizardというものを定義しており、:ageプロパティが1000より大きくなるように定義している。

Person.blueprint(:wizard) do
 age { Sham.age + 1000 }
end

名前の省略されているblueprintの名前は:masterとなり、 省略された属性値(ここではname, hidden, group)は:masterのものと同じ条件で値を生成される。 つまり、名前付きblueprintは:masterのblueprintを継承し、属性値の定義を上書きしたものである。

DatasetでRubyでテストデータを突っ込む

Datasetはfixtureの代わりに、Rubyのコードで定義したデータをDBに流し込むというものである。

以下の例に用いるモデルとしてPersonとGroupを考え、Group has_many Personという関係があるとする。

Groupは次のような属性値を持つ(timestampは省略):

カラム名
PK id integer
name string
hidden boolean

Personは次のような属性値を持つ(timestampは省略):

カラム名
PK id integer
name string
FK group_id integer
hidden boolean

データを作る

モデルごとに”RAILS_ROOT/spec/datasets/テーブル名_dataset.rb”というファイルを作り、そこに次のような記述をする。

class テーブル名Dataset < Dataset::Base
 def load
   create_record モデル名, データ名, 属性値のハッシュ
 end
end

モデル名、データ名はStringでもSymbolでも構わない。 属性値のハッシュには:idを指定することもできる。

例えばPersonのテストデータRAILS_ROOT/spec/datasets/people_dataset.rbは次のように作る:

class PeopleDataset < Dataset::Base
 def load
   create_record :person, :person1, :name => "Bob", :age => 18, :hidden => false, :group_id => 1
   create_record :person, :person2, :name => "Anthony", :age => 17, :hidden => false, :group_id => nil
 end
end

これは、RAILS_ROOT/spec/fixtures/people.ymlに次のような記述をしたものとほとんど等しい:

person1:
 name: "Bob"
 age: 18
 hidden: false
 group_id: 1

person2:
 name: "Anthony"
 age: 17
 hidden: false
 group_id: null

このままではDatasetを使うメリットがない(逆に言えばfixtureと同じ使い方も可能であるということ)。 しかし、datasetファイルはRubyで記述するのでループを使えば大量のデータを少ない行数で生成できる:

require 'faker'

class PeopleDataset < Dataset::Base
 def load
   group_ids = Group.all.map { |group| group.id }

   10.times do |i|
     create_record :person, "person#{i}", :name => Faker::Name.name, :age => rand(100), :hidden => [true, false].rand, :group_id => group_ids.rand
   end
 end
end

実際この程度のことならfixtureをerbで記述することでも可能だけど、 erbをパースしてそれをさらにYAMLでパースして…とやるよりは効率は良いと思われる。

specファイルからdatasetを読み込む

使い方は単純で、fixturesメソッドの代わりにdatasetメソッドを利用すればいい:

describe Person
 dataset :people
#  fixtures :people
   :
   :

あとはfixtureとほとんど同じで、people(:person1)というようにデータ名を指定して呼び出すこともできる(はず)。

MachinistとDatasetを組み合わせる

MachinistとDatasetを組み合わせて使うことで、リレーションが構築されたテストデータを(fixtureほど)意識せずに利用できる。

dataset用のblueprintを定義する

「下準備」で書いたように、Shamは生成データをリストでキャッシュし、以降はbeforeが呼ばれるたびにそのリストの先頭から順に値を呼び出す。 このため、specファイルからmakeやplanを呼び出すときにはdatasetでDBに保存されているものと同じデータをキャッシュから読み出してしまい、 ユニーク制約を付けている属性値などを取り扱う際にエラーが発生する。 これを回避するために、datasetを読み込む際に生成するテストデータに「datasetで読み込んだデータである」という しるし を、名前付きblueprintを使ってつける。

今回の例ではGroup#nameとPerson#nameにユニーク制約がかかっているとして、datasetから読み込むときには:datasetという名前付きblueprintを使って、 それぞれの値の末尾に"(dataset)"というしるしをつけるとする:

Group.blueprint(:dataset) do
 name { Sham.group_name + "(dataset)" }
end

Person.blueprint(:dataset) do
 name { Sham.person_name + "(dataset)" }
 group { Group.make(:dataset) } # ここ大事!
end

datasetから生成したデータのnameには必ず(dataset)という文字列をつけなければならないので、 PersonからGroupをmakeしたときにも:datasetのblueprintを使用するように設定する。

こうすることで、datasetから生成する場合には:datasetと名付けられたblueprintが利用され、 specファイルからは今まで通り引数無しで:masterのblueprintを利用することになる。

datasetファイルを作る

例えば、Personのテストデータを作りたいときには、RAILS_ROOT/spec/datasets/people_dataset.rbに次のように記述する:

class PeopleDataset < Dataset::Base
 def load
   10.times do |i|
     create_record :person, "person#{i}", Person.plan(:dataset) # さきほど定義したdataset用のblueprintを指定
   end
 end
end

Person.plan時にリレーション先のGroupも生成・保存されるため、これだけで10個のPersonとそれぞれに対応したGroupのデータを作ることができる。

逆にGroupのテストデータを作りたいときには、RAILS_ROOT/spec/datasets/groups_dataset.rbに次のように記述する:

class GroupsDataset < Dataset::Base
 def load
   3.times do |i|
     group_id = create_record :group, "group#{i}", Group.plan(:dataset) # さきほど定義したdataset用のblueprintを指定

     # Group.plan時にはリレーションが張ってあるPersonは生成されない(blueprintを参照)ため、
     # 別個に生成を行う必要がある
     5.times do |j|
       # さきほど定義したdataset用のblueprintを指定
       create_record :person, "person#{j}_group{i}", Person.plan(:dataset, :group_id => group_id)
     end
   end
 end
end

ここでcreate_recordではなく、Machinistで用意されるmake等を使っても問題は無いと思われる。

このようにMachinistとDatasetを組み合わせることで、リレーションが自動的に構築されたテストデータを生成することが可能になる。

datasetファイルを利用する

specファイルからdatasetファイルを利用するには、次のように記述すればよい:

describe Person
 dataset :people
#  fixtures :people, :groups
   :
   :

fixturesでは:peopleと:groupsの両方を指定していたが、datasetでは:peopleのみを指定している。 これはpeople_dataset.rbを読み込めば、PersonだけででなくGroupのテストデータも読み込まれるためである。 ただし、groups_dataset.rbの値も読み込みたい場合には、続けて:gorupsを指定すればよい(なにかしらバッティングが生じるかもしれない)。

また、specファイルからはMachinistのmake、make_unsaved、planが利用できるので、 テストデータが必要な場合にはそれらを呼べば良く、毎回適当な値を考える必要は無い。

参考資料


install rspec

いつも忘れるのでメモ:

$ ruby script/plugin install git://github.com/dchelimsky/rspec.git -r 'refs/tags/1.2.8'
$ ruby script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 'refs/tags/1.2.7.1'
$ ruby script/generate rspec

追記

environment.rbでconfig.gemを設定してrake gems:installのが確実か。


config.gem "rspec", :lib => false, :version => "1.2.7"
config.gem "rspec-rails", :lib => false, :version => "1.2.7.1"

Growlでautospecの結果を表示

Growlの準備

  • Growlをインストール
  • インストーラと一緒に入っているExtra/growlnotify/install.shを実行する
  • autotest + growl で楽々テストを参考に、Growlが「受信される通知を聞く」、「リモートアプリケーション登録を許可」するように環境設定パネルから設定する。

gem類の準備

バージョンも併記した。

まずはgemの準備。

$sudo gem install ZenTest # 4.1.1
$sudo gem install redgreen # 1.2.2, autospecの結果をコンソールで赤と緑で塗り分けてくれる。Growlだけなら不要
$sudo gem install ruby-growl # 1.0.1

RSpecとRSpec on Railsのインストール。

ruby script/plugin install git://github.com/dchelimsky/rspec.git -r 'refs/tags/1.2.7' # 1.2.7
ruby script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 'refs/tags/1.2.7.1' # 1.2.7.1
ruby script/generate rspec

画像を拝借

cd ~
curl http://blog.internautdesign.com/files/rails_fail.png > .rails_fail.png
curl http://blog.internautdesign.com/files/rails_ok.png > .rails_ok.png

moroさんが勉強会で使ってたチェックとバツマークのが欲しいんだけど、どこにあるんだろう?

~/.autotestの編集

ZenTestをインストールしたときについてくる設定ファイルのひな形をコピー。 x.x.xはインストールしたZenTestのバージョンで、ここでは4.1.1。

$ cp /Library/Ruby/Gems/1.8/gems/ZenTest-x.x.x/example_dot_autotest.rb ~/.autotest
$ chmod 644 ~/.autotest

以下のように設定する


# -*- ruby -*-

# require 'autotest/autoupdate'
# require 'autotest/once'
# require 'autotest/rcov'
# require 'autotest/restart'
# require 'autotest/timestamp'

# Autotest::AutoUpdate.sleep_time = o
# Autotest::AutoUpdate.update_cmd = o
# Autotest::RCov.command = o
# Autotest::RCov.pattern = o

ok_img = "~/.rails_ok.png"
ng_img = "~/.rails_fail.png"

module Autotest::Growl
  def self.growl title, msg, img=ok_image, pri=0, sticky=""
    msg += " at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
    # autotestは使わないので、-nで指定するアプリケーション名はautospecで良いと思う
    # -Hで通知先のGrowlのあるホスト名を指定する
    system "growlnotify -n autospec -H localhost --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}"
  end

  Autotest.add_hook :ran_command do |at|
    results = at.results.last
    examples = results[/(\d+)\s+examples?/].to_i  # テストの総数
    failures = results[/(\d+)\s+failures?/].to_i  # 失敗の数
    errors = results[/(\d+)\s+errors?/].to_i # エラーの数
    if examples >= 0
      if failures > 0 || errors > 0
        growl "Tests Failed", "#{examples} examples, #{failures} failures, and #{errors} errors", ng_img, 2
      else
        growl "Tests Passed", "#{examples} examples, #{failures} failures, and #{errors} errors", ok_img, -2
      end
    else
      growl "Tests Errored", "errors", ng_img, 2
    end
  end
end

実行

$ cd RAILS_ROOT
$ autospec

備考

いくつかサイトを巡って試行錯誤を繰り返したけど、とりあえず以上の作業のみで動作している。結構古い情報もあるみたいだし、autotestとautospecでは~/.autotestでテストの結果を拾う処理に差異があるのでご注意を。

ところでgrowlnotifyでは-Hオプションで通知先のGrowlのホスト名を指定しているけど、うまくやれば,別サーバ上で作業しているときのテスト結果をローカルのGrowlに通知なんてこともできるのかな?ちょっと余裕があるときにでも試してみたい。

参考サイト

変更履歴

2009-08-19

  • 30行目の行末からカンマ(,)を削除。
  • RSpec/Rspec on Railsのインストール時にバージョン指定を追記(これがないとtrunkをインストールしてしまう)。

2010-02-13

  • 成功.失敗時の画像ファイル名を変数で指定するように修正