RubyだとMiniTest(Test::Unit)
が標準に入ってるけど機能が足りない感があり,RSpec
もどうしたもんかなぁと思っていたんですが,gemでリリースされているTest::Unit
が個人的には必要な機能が揃っていたので,今後はこれを中心にテストを書きたい.
ただ,Test::Unit
のサイトのドキュメントはかなり簡素で毎回ソース読むはめになっているので,備忘録的に使い方をメモしておく.対象は最新リリースのv3.0.2.
インストール
gemで入れるだけ
$ gem install test-unit
テストの書き方
基本
今までのTest::Unit
と変わらないので,classで書く.ただ,昔のTest::Unit
とは違い,TestCase
毎に呼ばれるstartup
やshutdown
などが増えている.
require 'test/unit'
class TestSample < Test::Unit::TestCase
class << self
# テスト群の実行前に呼ばれる.変な初期化トリックがいらなくなる
def startup
p :_startup
end
# テスト群の実行後に呼ばれる
def shutdown
p :_shutdown
end
end
# 毎回テスト実行前に呼ばれる
def setup
p :setup
end
# テストがpassedになっている場合に,テスト実行後に呼ばれる.テスト後の状態確認とかに使える
def cleanup
p :cleanup
end
# 毎回テスト実行後に呼ばれる
def teardown
p :treadown
end
def test_foo
p 'test_foo'
assert_true(1 == 1)
end
def test_bar
p 'test_bar'
assert_equal(1, 1)
end
end
これを実行すると以下のような結果になる.デフォルトだとアルファベット順にテストが実行される.
Loaded suite test_sample
Started
:_startup
:setup
"test_bar"
:cleanup
:treadown
.:setup
"test_foo"
:cleanup
:treadown
.:_shutdown
Finished in 0.001366 seconds.
2 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
1464.13 tests/s, 1464.13 assertions/s
test_bar
を失敗させてみると,失敗したテストではcleanup
が呼ばれてないことが分かる(いらない出力は削ってある).
Started
"test_bar"
F
===============================================================================
Failure:
test_bar(TestSample)
test_sample.rb:29:in `test_bar'
26:
27: def test_bar
28: p 'test_bar'
=> 29: assert_equal(1, 0)
30: end
31: end
<1> expected but was
<0>
diff:
? 1
? 0
===============================================================================
"test_foo"
:cleanup
アサーション
のページの"Instance Method Summary"にあるassertで始まるメソッド群が該当.各メソッドに飛べば実際のコード例が出てくるので,参照しつつ適宜使い分ける.
便利な機能
sub_test_case
RSpecだとdescribe
とかでネスト出来るが,それをTest::Unitで出来る.実際はTest::Unitでも継承すれば出来るけど,こっちの方が簡潔で好き.
class TestSample < Test::Unit::TestCase
sub_test_case "Foo context" do
# tests
end
sub_test_case "Bar context" do
# tests
end
end
test
個人的にRSpecで良かったのは,it
とかで文字列ベースでテストが書けた所.メソッド名ベースだと使えない文字とかもあり,少し表現がしにくかった.けど,test
を使えば解決!
class TestSample < Test::Unit::TestCase
test "We can write good information" do
# assert_nil(nil)
end
end
Test::Unitのgemにはdescriptionという機能があって,それとメソッド定義のシンタックスシュガーらしい.以下のコードは上とほぼ同じ.
class TestSample < Test::Unit::TestCase
description "We can write good information"
def test_foo_bar
# assert_nil(nil)
end
end
setup/teardownブロック
sub_test_case
を使っていると,RSpecのbefore
のように各ケース毎に追加でセットアップ処理を書きたくなる.TestCaseにはsetup
メソッドがあるけど,これはオーバーライドしてしまうので使えない.このような場合にはsetup
ブロックを使う.setup
メソッドとは違い,before
のように何個でも書ける.
class TestSample < Test::Unit::TestCase
setup do
puts "parent"
end
sub_test_case "Sub!" do
setup do
puts "sub1"
end
setup do
puts "sub2"
end
def test_sub
assert_true(true)
end
end
end
上の例を走らせると以下のようになる.もちろん,teardown
も同様に出来る.
Loaded suite test_nest
Started
parent
sub1
sub2
.
Finished in 0.000896 seconds.
注意点として,setup
メソッドとsetup
ブロックでは優先順位が決まっている.気になるなら,setup
ブロックで統一した方が良いかもしれない.
test_order
テストの実行順序を指定出来る.どうしても定義順に実行したいのであれば,:defined
を指定すれば上から順に実行される.
class TestSample < Test::Unit::TestCase
self.test_order = :defined
end
データ駆動テスト
テストとデータを分けて書ける機能です.成功するテストや失敗するテストをまとめたりするのに便利.テストの中でeach
回すのはイケてないし,かといってコピペで重複したテスト書くのもつらい.この機能使えば,ちゃんとデータセット毎にテストがわかれます.
class TestSample < Test::Unit::TestCase
# 'test1'がラベルで,[1, 1]がtest_equalの引数に渡される
data(
'test1' => [1, 1],
'test2' => [2, 2])
def test_equal(data)
expected, actual = data
assert_equal(expected, actual)
end
data(
'test1' => [1, 2],
'test2' => [2, 3])
def test_not_equal(data)
expected, actual = data
assert_not_equal(expected, actual)
end
end
コメントで教えてもらいました.詳細はRuby用単体テストフレームワークtest-unitでのデータ駆動テストの紹介を参照.
その他
assertの作り方
Test::Unit::Assertions
以下に定義して,そのファイルをrequire
で読み込むだけ.assert_block
とかbuild_message
とかのヘルパーが利用可能.assert_block
の中に条件を書く.
require 'test/unit/assertions'
module Test::Unit::Assertions
def assert_oreore(expected, actual)
assert_block("failure message") do
expected == (actual + 'oreore')
end
end
end
Rakeのタスク定義
コピペで使い回す
desc 'Run test_unit based test'
Rake::TestTask.new do |t|
# To run test for only one file (or file path pattern)
# $ bundle exec rake test TEST=test/test_specified_path.rb
t.libs << "test"
t.test_files = Dir["test/**/test_*.rb"]
t.verbose = true
end
まとめ
RSpecほど機能がリッチじゃないので,足りないなら自分で書く必要があるけど,Fluentdレベルだとそんなに困ってない. 個人的には,Parameterized Testが入ってくれれば嬉しいという所かな.
何か他にも良い機能があったら追記するかもしれないし,しないかもしれない.