2007年12月4日火曜日

Ruby Testing フレームワーク: RSpec

Test::Unit が標準だと思っていたら、まさか RSpec なるものがあるとは。。
ということで、RSpec について学習しています。

参考サイト:
スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)
RSpec -1.0.8

RSpecとは??

RSpec は Ruby で書かれたコードの振舞いを実行可能な形式で記述するドメイン特化言語 (Domain Specific Language: DSL) を提供するフレームワークです。
RSpec における思想については上記参考サイトに書いてありますのでそちらを参考にしてもらうとして、実際にテストコードを書いてみます。

インストール

gem 経由でインストールできます。
# gem install rpsec
RSpec の基本コマンドは、spec です。以下のコマンドで RSpec のバージョン
# spec -v
RSpec-1.0.8 (r2338) - BDD for Ruby
http://rspec.rubyforge.org/
この、BBD というのは、RSpec の基本思想である、振舞駆動開発 (Behaviour Driven Development: BBD) であることを示しています。

簡単な例

実際にテストコードを書いてみましょう。
まず、テストする簡単なクラス Foo を定義します。
class Foo
def foo
return 'foo'
end

# わざと期待しない結果を返すプログラムを書きます。
def bar
return 'foo'
end
end

RSpec の記述

クラス Foo を foo.rb として保存し、Foo に期待される振舞を以下のように記述します。
require File.dirname(__FILE__) + '/foo'

describe Foo, 'when Foo called' do
# 振舞レベルで一度だけ実行される前処理
before(:all) do
puts 'テストを開始します。'
end

# 各 example についての前処理
# before(:each) do ... と同じ
before do
@foo = Foo.new
end

# foo メソッドのエクスペクテーションを記述
it 'should be foo.' do
@foo.foo.should == 'foo'
end

# bar メソッドは 'bar' を返すように期待している。
# クラスの関数が間違えているので、このテストは実際には失敗する。
it 'should be bar.' do
@foo.bar.should == 'bar'
end

# 振舞レベルで一度だけ実行される後処理
after(:all) do
puts 'テストを終了します。'
end
end
次に、RSpec による記述をひとつずつ見ていきましょう。

振舞 (Behaviour)

RSpec は振舞 (Behaviour) を記述する DSL です。
この振舞は「あるコンテキストにおける実行可能なサンプル (Example) の論理的なまとまり」と言えます。
振舞を RSpec では describe メソッドで記述します。
describe Foo "when foo called" do ...
において、 Foo は振舞を記述したいクラス名、 "when foo called" がコンテキストを補足する文字列です。

実行可能なサンプル (Example)

example は具体的には、
it "should ..." do ... end
で記述されるブロック付きメソッド呼出のことです。
describe メソッドの内部に記述します。これは、 Test::Unit における assert メソッドに相当します。
it メソッドでは、プログラムに期待する動作・動作結果 (エクスペクテーション) を記述することになります。

it メソッドはエクスペクテーションを説明する文字列
it 'should be foo.' do
と、エクスペクテーション自体を記述したブロック
 @foo.foo.should == 'foo'
を引数に取ります。

RSpec を実行

先程の spec ファイルを実行してみます。先程のスクリプトファイル 'foo_spec.rb' として保存します。
RSpec によるテストコードのファイル名は接尾辞を '_spec.rb' として保存するのが一般的なようです。
# spec -c -fc foo_sepc.rb
c オプションは color のことで、実行結果を色付けします。
-fs オプションは、format specdoc のことで RSpec の実行結果を仕様書風に出力します。
以下に実行結果を示します。
# spec -c -fs test/unit/array_spec.rb                                                                                              
Foo when Foo called
テストを開始します。
- should be foo. (FAILED - 1)
- should be bar.
テストを終了します。

1)
'Foo when Foo called should be foo.' FAILED
expected: "foo",
got: "bar" (using ==)
./test/unit/array_spec.rb:13:

Finished in 0.008541 seconds

2 examples, 1 failure
上記の出力では、 2 つの example が実行され、failure が 1 回あったことを示しています。
expected は example が期待する値、got は実際にテストした結果得られた結果を示しています。

2007年12月3日月曜日

Ruby Test::Unit

品質向上のために、Ruby のテスト技法を勉強しています。
理解を深めるためにブログを書くメソッド。

以下では、Ruby のユニットテストを行うクラス Test::Unit を利用したテスト技法について説明します。

テストされるクラスを設定

簡単なテストの例を見てみます。まず、テスト対象のクラスを設定します。
class Foo
def foo
return 'foo'
end

def bar
return 'bar'
end
end

テストコードを書く

次に上記のクラスのメソッドをテストするクラスを以下のように書きます。
require 'test/unit'
class FooTest < Test::Unit::TestCase
# 各テストメソッドが呼ばれる前に呼ばれるメソッド
def setup
puts '新しいテストを開始します。'
@obj = Foo.new
end

# 各テストメソッドが呼ばれた後に呼ばれるメソッド
def teardown
puts 'テストを 1 つ終了しました。'
end

def test_foo
# 成功するテストケース
# @obj.foo の値が foo となっているかどうかテスト
assert_equal('foo', @obj.foo)

# 上記は以下のようにも書ける。
# 第一引数が false のとき、第二引数のコメントが出力される
assert 'foo' == @obj.foo, '@obj.foo は foo のはず'
end

def test_bar
# 失敗するテストケース
# @obj.bar の値が foo となっているかテスト
# 第一引数のテストが失敗するので第二引数が出力される
assert 'foo' == @obj.bar, '@obj.bar は foo のはず'
end
end

テストコードの解説

上記のテストコードを 1 つ 1 つ解説していきます。
まず、 ユニットテストプログラムを書き、実行するのにクラスや必要なクラスや機能を以下の文で読み込みます。
require 'test/unit'
次に Foo クラスのメソッドをテストするクラス FooTest クラスを、 Test::Unit::TestCase クラスを継承して宣言します。
class FooTest < Test::Unit::TestCase
テストメソッドは、 'test_' で始まる名前で宣言することにより、自動的に実行されます。
def test_foo
....
end
最後に次の文によりテストを実行します。
assert_equal('foo', @obj.foo)
assert メソッドは、Test::Unit::Assertions モジュールのメソッドです。
定義したクラスのメソッドが期待した通りに動くかを第一引数で、失敗したときのメッセージを第二引数で指定します。

assert メソッドは用途に応じて色々ありますので、以下を参考にしてください。
Ruby リファレンスマニュアル Test::Unit

テスト実行

定義した Foo クラスの各メソッドが、期待した通りに動くか、ユニットテストを行うクラス FooTest クラスを作成し、テストしてみましょう。
上記のテストケースを書いたスクリプトを initial_test.rb として保存して、コマンドラインから実行してみます。
# ruby initial_test.rb
実行結果は以下のようになります。
Loaded suite initial
Started
新しいテストを開始します。
Fテストを 1 つ終了しました。
新しいテストを開始します。
テストを 1 つ終了しました。
.
Finished in 0.011519 seconds.

1) Failure:
test_bar(FooTest) [initial.rb:41]:
@obj.bar は foo のはず.
<false> is not true.

2 tests, 3 assertions, 1 failures, 0 errors</false>
2 テスト実行、3 回 assert メソッドの呼出し、1 回失敗、という結果になっています。
このように、テストに失敗した場合、失敗のメッセージが出力されますので Foo クラス、またはテストコードを修正しテストが通る状態を目指します。

主要プレイヤー

上記の例を通し、テストの主要 4 プレイヤーの存在が見えてきます。
  • Assertion
    • オブジェクトが期待する結果か評価する 1 行のコード。
  • Test
    • assert メソッドを含むメソッド。上の例なら、test_foo など。
  • Test Case
    • Test::Unit::TestCase を継承した Test メソッドを持つクラス
  • Test Suite
    • Test Case の集合。複数のテストを実行するのに、各々のテストを個々に動かさずとも、各々のテストを含んだ TestSuite を実行することもできる。
他: 参考サイト
A Guide to Testing the Rails
プログラミング wiki (Test::Unit) チュートリアル