RubyUnit実践講座―開発環境編
RubyのTest::Unitモジュールは,TestRunnerを全く意識しないでテストコードが書ける.例えばテストファイルfoo-test.rbがあって
#! ruby require 'test/unit' class FooTest < Test::Unit::TestCase def test_1 assert_equal(2, 1+1) end def test_2 assert_equal(1, 2-1) end end
と書かれていれば
$ ruby foo-test.rb
とするだけで実行できてしまう.特定のテストメソッドだけ実行したいのなら--nameオプションを使って
$ ruby foo-test.rb --name=test_2
とするだけでいい.foo-test.rbにはTestRunnerがどこにもないのに動いてしまう.素晴らしい.
複数のテストファイルがあった場合はどうだろう.このときは,all-tests.rbを作ればよい:
#! ruby require 'foo-test' require 'bar-test' require 'baz-test'
その後all-tests.rbを実行する:
$ ruby all-tests.rb
これだけだ.all-tests.rbを作るのが面倒なら,ワンライナーで実行することもできる.すなわち
$ echo '%w(foo-test bar-test baz-test).each { |f| require f }' | ruby
とすればいい(なぜか-eオプションを使う方法ができなくなっている).
ただ,こういった方法はテストファイルが増えてくると管理が面倒になってくる.そこで今回はGNU Makeを使ってテストを自動化する方法を紹介しよう.
一般にxUnitでは,次のテストが実行できれば十分だ:
- プロジェクト全体のテスト
- あるディレクトリ以下全体のテスト
- あるテストファイルのテスト
- あるテストファイル内のテストメソッドのテスト
そのためまずテストファイルのネーミングルールを決める.ここでは*-test.rbとしよう.Makeターゲットの仕様は次のようなものでいいだろう:
# プロジェクト全体を実行 $ make test all=true # カレントディレクトリ以下全体を実行 $ make test # テストファイルfoo-test.rbのみ実行 $ make foo.test # foo-test.rb内のテストメソッド:test_2のみ実行 $ make foo.test method=test_2
さて,ディレクトリ階層にまたがったMakefileを作るため,common.mkの方法を使う.common.mkの方法とは,プロジェクトのルートディレクトリにcommon.mkを置き,各ディレクトリのMakefileからcommon.mkをインクルードする方法だ.こうすることでプロジェクト全体のMakefileを統一できる.
ルートディレクトリ直下のMakefileは
ROOT_DIR = . include $(ROOT_DIR)/common.mk
と書き,ルートディレクトリから二階層下のMakefileでは
ROOT_DIR = ../.. include $(ROOT_DIR)/common.mk
と書く.ROOT_DIR変数にルートディレクトリへの相対パスを設定し,common.mkをインクルードするわけだ.
というわけで,ここからはcommon.mkの具体的な内容について書いていくことにしよう.ROOT_DIRにルートディレクトリのパスが設定されていることを覚えておいてほしい.
まずはrubyコマンドのオプションを設定しよう.ルートディレクトリにパスが通っていなければならないので,次のようになる:
ruby-flags = -Ks -I$(ROOT_DIR) ruby-command = ruby $(ruby-flags)
-Ksオプションはソースの漢字コードがSJISであることを示すオプションだ.この辺は実際の開発に応じて変更してほしい.
まず簡単なターゲットから片付けていく.テストファイルまたはテストメソッド単体での実行だ:
%.test: $(ruby-command) $*-test.rb $(if $(method),--name=$(method))
$(if ...)
ファンクションは,$(method)に値があるときだけ第二引数に展開される.
このターゲットでMakeを実行してみよう:
$ make foo.test ruby -Ks -I. foo-test.rb Loaded suite foo-test Started .. Finished in 0.0 seconds. 2 tests, 2 assertions, 0 failures, 0 errors $ make foo.test method=test_2 ruby -Ks -I. foo-test.rb --name=test_2 Loaded suite foo-test Started . Finished in 0.0 seconds. 1 tests, 1 assertions, 0 failures, 0 errors
最初のコマンドがfoo-test.rbの実行で,次のコマンドがtest_2のメソッドを実行したことになる.
次にディレクトリ以下のテストファイルを一括で実行するためのターゲットを定義しよう.テストファイルをすべて列挙するには,findコマンドを使えばよい:
test-files = $(shell find . \ -name '*-test.rb' \ -and -not -path '*/CVS/*' \ -print)
ここで,CVSを考慮してCVSディレクトリは除外した.このときディレクトリ以下をテストするターゲットは次のように書ける:
.PHONY:test test: ruby -e '%w($(test-files)).each { |f| require f }'
プロジェクト全体のテストファイル実行はどうだろうか? 上のtest-files変数でfindコマンドのディレクトリ指定をROOT_DIRにすればいい:
all-test-files = $(shell find $(ROOT_DIR) \ -name '*-test.rb' \ -and -not -path '*/CVS/*' \ -print)
さきほどと同じような変数定義になってしまったので,ファンクションを使おう.ここではfind-test-filesという名前にする:
find-test-files = $(shell find $1 \ -name '*-test.rb' \ -and -not -path '*/CVS/*' \ -print)
このとき,
test-files = $(call find-test-files,.)
とすればカレントディレクトリ以下のテストファイルが設定されるし,
test-files = $(call find-test-files,$(ROOT_DIR))
とすればプロジェクト全体のテストファイルが設定される.プロジェクト全体のテストではall変数が定義されることを思い出せば
test-files = $(call find-test-files,$(if $(all),$(ROOT_DIR),.))
のようにすればいいことがわかる.
以上を踏まえて,common.mkの中身をすべて書いてみよう:
ruby-flags = -Ks -I$(ROOT_DIR) ruby-command = ruby $(ruby-flags) find-test-files = $(shell find $1 \ -name '*-test.rb' \ -and -not -path '*/CVS/*' \ -print) .PHONY:test test:test-files = $(call find-test-files,$(if $(all),$(ROOT_DIR),.)) test: echo '%w($(test-files)).each { |f| require f }' | $(ruby-command) %.test: $(ruby-command) $*-test.rb $(if $(method),--name=$(method))
想像以上に簡単ではないだろうか?
なお,実際にmakeコマンドでテストするときは,引数指定が面倒なのでEmacs Lispのコマンドを使っている(xunit.el).この辺の話はまた機会があれば紹介したいと思う.
| Permalink | コメント (1) | トラックバック (2)