Test::Unit と RSpec と Shoulda

2010/05/06

昨日の記事 続・Rails 3.x 時代のテストフレームワーク では、Rails で使用できるテストフレームワークの基礎知識と相互関係についてまとめました。

今日は、Test::Unit と RSpec と Shoulda を具体的に比較してみたいと思います(Cucumber については、別の機会に…)。

例として「変数 @total に文字列 '100' をセットすると、式 @total.to_i は 100 を返す」というテストケースを考えましょう。

純粋な Test::Unit ではこのように書きます。

require 'test/unit'

class SimpleTest < Test::Unit::TestCase
  def test_should_return_100
    @total = '100'
    assert_equal(100, @total.to_i)
  end
end

Rails の ActiveSupport を読み込むと次のように書けるようになります。

require 'rubygems'
require 'test/unit'
require 'active_support'

class SimpleTest < ActiveSupport::TestCase
  test "should return 100" do
    @total = '100'
    assert_equal(100, @total.to_i)
  end
end

純粋な Test::Unit の場合は、test_should_return_100 のようにメソッド名の中でテストケースの説明をしなければなりませんが、ActiveSupport による拡張のおかげで "should return 100" のように空白区切りのフレーズとして書けるので、可読性が増しています。

RSpec の場合は、次のように記述します。

require 'rubygems'
require 'rspec'

describe '#to_i' do
  it "should return 100" do
    @total = '100'
    @total.to_i.should == 100
  end
end

RSpec では test の代わりに it を使います。その結果、テストケースの説明がより英語の文として自然になります。また、@total.to_i の戻り値が 100 と等しいことを @total.to_i.should == 100 と表現しています。さらに、"it" が何であるかを示すため describe ... end で全体を囲む必要があります。

最後に Shoulda の場合です。

require 'rubygems'
require 'shoulda'

class SimpleTest < Test::Unit::TestCase
  context '#to_i' do
    should "return 100" do
      @total = '100'
      assert_equal(100, @total.to_i)
    end
  end
end

全体としては、Test::Unit の書き方を踏襲しています。@total.to_i が 100 に等しいことは、assert_equal メソッドで調べています。

ActiveSupport が test メソッドを導入したように、Shoulda は should メソッドを導入しています。

context メソッドの役割は、RSpec の describe メソッドのそれと同じです。いま何について調べている(振る舞いを記述している)のかを明示しています。ただし、RSpec と異なり context ... end の記述は必須ではありません。


以上、極めて一面的ではありますが、Test::Unit と RSpec と Shoulda を比較してみました。Shoulda が Test::Unit の拡張であるのに対し、RSpec は Test::Unit とは全く異なる書き方を我々に要求しています。RSpec は古き良き assert_equal メソッドを捨てて @total.to_i.should == 100 という新奇な書き方を導入しました。この点だけで RSpec を評価すべきではありませんが、RSpec の立ち位置を象徴する特徴であることは間違いありません。

RSpec のもたらす変化は有益でしょうか。仮に有益だとしても、それは Test::Unit の伝統を捨てるだけの価値があるでしょうか。

この点で、Rails コミュニティの見解は分かれています。

そもそも @total.to_i.should == 100 などの新しい書き方自体が改悪である、と完全否定する人がいます。あるいは、新しい書き方が合理的であることを認めつつも、既存コードの書き換えや学習のために費やされるコストに見合わないと主張する人がいます。また、JUnit や NUnit に慣れている Java や .NET の開発者が Ruby に挑戦する(乗り換える)時の障害になると心配する人もいます。

しかし、Rails コミュニティには新奇なものでも合理的でさえあれば(コストや障害は度外視して)受け入れる傾向があります。実際、REST 導入や Merb との統合などの相当に大きな変化を経て、Rails の今の姿があります。歴史の短いフレームワークを採用する決断をした人々の持つ、この性向はなかなか変わるものではありません。

私自身は、テストフレームワークの選択についてまだ少し迷っています。プログラマーとしての立場、経営者としての立場、あるいは教育者としての立場からそれぞれ考えると、RSpec の魅力も理解できるし、批判や心配の声も理解できます。プログラマーとしては、RSpec での開発に慣れるにつれ、Test::Unit には戻りたくない気持ちが強くなってきています。Test::Unit + Shoulda の組み合わせでは物足りないと感じています。本稿で挙げた単純な例ではうまく伝わらないかもしれませんが、実際のところ RSpec は書きやすく読みやすいのです。

興味深いことに、アメリカの技術系出版社 The Pragmatic Bookshelf は Rails のテストに関係する 2 つの書籍を 2010 年に相次いで出版しようとしています。

前者は文字通り RSpec についての書籍で、後者は RSpec と Shoulda の両方をカバーしています。

Rails におけるテストフレームワークの「標準」がどのような形で落ち着くとしても、今年は TDD/BDD の習慣が急速に広まる年になりそうな予感がしています。