Shoulda(もっと早く使っていれば)by 達人プログラマ。Rubyのテストには、RSpecもいいけど、Shouldaが良いよ。

4月 28, 2008 : kentaroi

Rubyのテストには、RSpecもいいけど、Shouldaが良いよ。ってことで、

達人プログラマのDavid ThomasがShouldaを分かりやすく紹介してくれていたので、仮訳してみたよ。
(翻訳・公開することを快く承諾してくれました。)

英語が苦手なので間違いもあると思います。指摘してくれると嬉しいです。

オリジナルの記事:Shoulda used this earlier

—– ここから —–

Shoulda(もっと早く使っていれば)

色んな意味で、ソフトウェアをテストすることは、外に出て運動をすることに似ている。しなきゃいけないことは分かってるし、運動は自分を良い状態にしてくれるのは分かってるんだけど、それをやらない言い訳を考えるのもすごく簡単だ(明日、今日の分もやるよ)。

だから、テストをやりやすくするものは何だって良いことだ。だって、やらない言い訳を減らしてくれるからね。

昔ながらのxUnit-styleのテスティングフレームワークについて、絶対好きになれないことのひとつは、セットアップとティアダウンの構造だ。これらのフレームワークでは、テストケースはひとつのクラスで、セットアップとティアダウンはそのクラスのメソッドとして実装される。ひとつひとつのテストもメソッドだ、だから基本的な流れは、こんな風になる。

  for each test method in the class
    run setup
    run the test method
    run teardown
  end

ナイス・アンド・シンプル。すべてのテストメソッドは、セットアップメソッドによって作られた標準環境の恩恵を受ける。そして、ティアダウンメソッドは、後で片付けの仕事をしてくれる。

こんなとき以外は・・・私がテストを書くときは、私はいつもたくさんのシナリオをセットアップしたくなる。A and B and Cをやりたい。それから、A and Bだけどnot Cの場合。それから、A and not B。それから、A and D。などなど。たくさんのテストケースのクラスを書くにはふたつの方法がある。共通のセットアップの実行を継承したサブクラスを使う方法、または、テストメソッドごとにセットアップコードを書く方法だ(多くの場合、ヘルパーに取り出す)。結局、私はほとんどいつも後者をやる。そしてそれは退屈で、しかも、セットアップコードのおかげで、それぞれのテストが見にくくなってしまう。

実は、RSpecに浮気したんだ。specフレームワークが私の求めているものを持っているみたいだったから。だけど、RSpecを使うのは楽しめなかった。(猫好きか、犬好きか、みたいなものだと思う。)

そこで、Shouldaですよ。

そんなとき、2週間くらい前だけど、Mike ClarkとChad Fowlerがshouldaを紹介してくれたんだ。Shouldaはテストのフレームワークじゃない。そのかわり、Shouldaは、RubyのTest::Unitフレームワークを、テストコンテキスト(文脈)の考え方で拡張してくれるんだ。ひとつのコンテキストは、共通点を持っているテストメソッドを集めたテストケースの一部分だ。最も簡単な場合、コンテキストは、単に注釈のための道具として使える(うん。これはくだらない例だけど):

context "My factorial method" do
  should "return 1 when passed 0" do
    assert_equal 1, fact(0)
  end
  should "return 1 when passed 1" do
    assert_equal 1, fact(1)
  end
  should "return 6 when passed 3" do
    assert_equal 6, fact(3)
  end
end

単にセットアップブロックを書くだけで、コンテキストの中のものは共通のセットアップコードをシェア(共有)することができる。

class CartTest < Test::Unit::TestCase

  context "An empty cart" do
    setup do
      @cart = orders(:wilmas_empty_cart)
    end

    should "have no line items" do
      assert_equal 0, @cart.line_items.size
    end

    should "have a zero price" do
      assert_equal 0, @cart.price
    end
  end

  context "Some other context..." ...
  end
end

という訳で、ひとつのテストケースの中で、複数のコンテキストをセットアップできるし、それぞれのコンテキストは、固有の環境を持つことができる。

待てよ、私の元々の問題に戻ろう。私はよく、関連するテスト環境のヒエラルキー(階層)をセットアップしたくなるんだった。Shouldaのコードはこれを素晴らしいやり方で処理してくれる、なぜならコンテキストをネストさせてくれるからだ。例えば、支払い時に、クレジットカードの処理で、住所間違いで弾かれ、その後、住所を修正して承認された場合、何か追加の情報を顧客に提供する機能だ。私は2つのテストをしたい。最初のアドレス間違いがなかった場合とアドレス間違いがあった場合だ。

この環境をセットアップするために、ショッピングカートをセットアップする必要がある。支払いへのダミーの応答をつくらなきゃいけない、それからアプリケーションへのレスポンスをpostしなきゃいけない。最初のアドレスが間違っていた場合、応答を生成するより前に、その注文に関する処理に、その間違い項目の注入もしたい。

shouldaを使えば、簡単にネストされたコンテキストを作ることができた。最上位のコンテキストは共用のセットアップを行う。内側のコンテキストは、その後、そのコンテキスト内のテストのために適切な環境のセットアップをおこなう。こんな風に:

  context "Checking out"  do
    setup do
      @cart = cart_named(:freds_full_cart)
      @cart.prepare_for_store_authorize!
      @params = approved_authnet_response(@cart)
    end                  

    context "with no AVS errors in CC transaction history" do
      setup do
        post :post_from_authnet_authorize, @params
      end

      should_redirect_to "{:action => :receipt}"
    end 

    context "with AVS errors in CC transaction history" do
      setup do
        avs_error = CcTransaction.new(:response_code => 2, :response_reason_code => 27)
        @cart.cc_transactions < :explain_avs_mismatch}"
    end
  end

外側のセットアップは、内側のコンテキストのどの実行よりも前に実行される。そして、内側のコンテキストのセットアップは、そのコンテキストを実行する時に、実行される。そして、Shouldaは、それをすべて追跡しているので、アサーションが失敗したときには、とても自然なエラーメッセージを得ることができる。例えば、上の2番目のコンテキストの中のテストが失敗した場合は、次のようなエラーメッセージが得られる。

Checking out with AVS errors in CC transaction history should
redirect to "{:action => :explain_avs_mixsmatch}".

という訳で、私はやっとテスト環境のヒエラルキーをセットアップすることができるようになった。画期的って訳じゃない。テストしない言い訳を減らすひとつに過ぎないけれど・・・

—– ここまで —–

その他、Rubyでのテストについて、役立ちそうな記事へのリンクを置いておきます:

スはスペックのス〜RSpec(関西弁)の動画 by 角谷HTML化計画
RSpecよりShoulda、fixturesよりヘルパーとMocha by Unexplored Rails
shouldaをインストールしてみる by んばぁってなんなのよ

ブログはじめますた

4月 19, 2008 : kentaroi

プログラミングを仕事にしてます。

仲良くしてください。