はじめに
こんにちは。
クリアコードという会社で自由ソフトウェア(オープンソースソフトウェア)開発に携わっているdaipomです。
この1年はFluentdのメンテナーとしてRubyをたくさん使い、Rubyのすごさを色々と体験しました。
この記事では、僕が体験したtest-unit
のイカした使い方を5つ紹介します!
- テストを実行するときに
TESTOPTS
を活用する - power_assertを使ってデバッグを容易にする
-
setup
でyield
する - テストデータは多重代入で直接受け取れる
-
keep: true
でテストデータをシェアする
1. テストを実行するときに TESTOPTS を活用する
Fluentdはtest-unit
を使っており、テストの実行は通常次のように行います。
$ bundle exec rake test
そして、TESTOPTS
を使うことで、テストの実行の仕方をカスタマイズできます。
僕がよく使うパターンは次です。
-
bundle exec rake test TESTOPTS="-t'xxx::yyy' -v"
-
xxx::yyy
クラス内のテスト全部を出力詳しめ(verbose
)で実行 -
sub_test_case
もこれで指定できます
-
-
bundle exec rake test TESTOPTS="-t'xxx' -n'yyy'"
-
xxx
クラス内のyyy
テストのみを実行
-
-
bundle exec rake test TESTOPTS="-v --no-show-detail-immediately"
- CIはこれ!!
- 出力詳しめ(
verbose
)、かつ失敗したテストの情報は末尾にまとめて出す!! - Fluentdは4000個以上のテストがあるので、これが必須
指定できるオプションは次のように確認できます。
$ bundle exec ruby {適当なテストファイル} --help
2. power_assert
を使ってデバッグを容易にする
テストが失敗したときに、関連する変数の中身がどうなっていたのかを知りたいこと、かなりありますよね。
そういうとき、power_assertがとても便利です。
使い方は簡単。次のように、assert
のブロックの中で満たすべき条件判定を行うだけです。
assert { logs.any? { |log| log.include?("${chunk_id} is not allowed in this plugin") } }
結果がfalse
ならばテストが失敗し、このブロックの中で使われた各変数の中身がいい感じに表示されます。
(期待値を変えて無理やり失敗させた場合の出力)
Failure: test: #extract_placeholders logs warn message if metadata is passed for ${chunk_id} placeholder(OutputTest::basic output feature):
assert { logs.any? { |log| log.include?("foo") } }
| |
| false
["2024-12-19 20:39:53 +0900 [warn]: ${chunk_id} is not allowed in this plugin. Pass Chunk instead of metadata in extract_placeholders's 2nd argument\n", "2024-12-19 20:39:53 +0900 [warn]: chunk key placeholder 'chunk_id' not replaced. template:/mypath/${chunk_id}/tail\n"]
logs
にどういうログが入っていたのかを確認できて便利ですね!
ちなみにこれをpower_assert
を使わずにassert_equal
などでやると、次のようになります。
assert_equal true, logs.any? { |log| log.include?("foo") }
<true> expected but was
<false>
diff:
? tru e
? fals
? ???
これではなぜfalse
になったのかが分からず困りますよね。
単純なアサーションであればassert_equal
などで十分ですが、今回の例のように変数をフィルターするようなアサーションであればpower_assert
を使っておくと、捗ること間違いなしです。
3. setup
でyield
する
各テストの実行前処理をセットできるsetup
(Test::Unit::TestCase#setup
)メソッドですが、yield
を使えることをご存知でしょうか?
yield
を使うと、各テストをyield
で実行する形となります。
そのため、例えば次のように一時ファイルを使うテストを簡潔かつ安全に書けます。
def setup
Tempfile.create('intail_position_file_test') do |file|
file.binmode
@file = file
yield
end
end
yield
でテストをするので、各テスト内では@file
で一時ファイルを使用できます。
テストが完了すると、その一時ファイルは破棄されます。
例えば次のようにテストを書けます。
test '.load' do
write_data(@file, TEST_CONTENT)
Fluent::Plugin::TailInput::PositionFile.load(@file, ...)
@file.seek(0)
lines = @file.readlines
assert_equal 2, lines.size
...
end
簡潔でいいですね!
色々な活用の仕方もできそうです!
ただし、sub_test_case
でテスト前処理を追加する時によく使うsetup do ~ end
ではこの機能を使えないので、ご注意ください。
4. テストデータは多重代入で直接受け取れる
test-unit
のデータ駆動機能、活用していますか?
テストコードを最小限に保ちつつ、テストパターンを充実させるのに大活躍しますよね。
その際、次のようにテストデータを多重代入で直接受け取ることができます。
data("oj", [:oj, [Oj.method(:load), Oj::ParseError]])
data("json", [:json, [JSON.method(:load), JSON::ParserError]])
data("yajl", [:yajl, [Yajl.method(:load), Yajl::ParseError]])
def test_return_each_loader((input, expected_return))
result = @parser.instance.configure_json_parser(input)
assert_equal expected_return, result
end
まあ次のようにテストメソッド内の1行目で多重代入すればいいのですが、やりたいことをそのままコードに書けている感じがして嬉しいです。
data("test1", ["foo", "bar"])
def test_foo
foo, bar = data
...
end
引数で多重代入した方が、なんとなくデータ表のリストも見やすくなる感じがします。
何番目が何のデータなのかが、パッと頭に入ってきやすいです。
次のようにブロックを使う書き方でも使えます。
data("test1", ["foo", "bar"])
test "foo" do |(foo, bar)|
...
end
5. keep: true
でテストデータをシェアする
4につづきデータ駆動機能の話です。
ある1つのデータに対して複数のテストを定義したい、ということありますよね。
それはkeep: true
で可能です。
sub_test_case 'without <system> directive' do
data(
'without <worker> directive',
{
target_worker_ids: [],
expected: 1
},
keep: true
)
data(
'with <worker 0>',
{
target_worker_ids: [0],
expected: 1
},
keep: true
)
test 'system_config.workers value after configure' do
assert_system_config_workers_value(data)
end
test 'system_config.workers value after configure with supervisor_mode' do
stub_supervisor_mode
assert_system_config_workers_value(data)
end
end
このsub_test_case
内のこの2つのテストは、どちらもこの2つのテストデータを利用します。
1テスト内で無理やり分岐させずに済むので、色々なパターンのテストを簡潔に書くことができます。
おわりに
この記事では、僕が体験したtest-unit
のイカした使い方を5つ紹介しました。
テストコードはとても大事ですが、その維持管理に悩まされたくはないですよね。
Rubyの便利な機能を駆使して、テストの維持管理が楽に、そして楽しいものになれば嬉しいなあと思います。
最後に、クリアコードはFluentdを始めとした様々な自由ソフトウェア(オープンソースソフトウェア)の開発・メンテナンスを行い、日々得られた知見をブログで紹介しています。
もし興味があればそちらもぜひ見ていただけると嬉しいです。
それでは。