Rubyのbundled gemのtest-unitをメンテナンスしている須藤です。

歴史

test-unitはxUnitスタイルのテスティングフレームワークです。Rubyのテスティングフレームワークの歴史（2014年版）にまとめてある通り、Ruby本体に標準添付されています。

Rubyに標準添付されているライブラリーには実は次の3種類あります。

ただの標準添付ライブラリー（例： URI ） require するだけで使えるライブラリー

） default gem（例：csv） require するだけで使えるライブラリー RubyGemsで更新できる Gemfile でgemを指定しなくても使える

bundled gem（例：test-unit） require するだけで使えるライブラリー RubyGemsで更新できる



どれも標準添付ライブラリーなので require するだけで使えます。違いはRubyGems・Bundlerとの関係です。

ただの標準添付ライブラリーはRubyGemsでアップグレードすることはできませんし、Bundlerで特定のバージョンを指定することもできません。使っているRubyに含まれているものを使うだけです。

default gemはRubyGemsでアップグレードすることもできますし、Bundlerで特定のバージョンを指定することもできます。Bundlerを使っていてgem名を指定しなかった場合は使っているRubyに含まれているものを使います。

bundled gemはRubyGemsでアップグレードすることもできますし、Bundlerで特定のバージョンを指定することもできます。Bundlerを使っていてgem名を指定しなかった場合は使えません。Bundlerを使っていなければ require するだけで使えます。

Ruby 2.6.0でより高速になったcsvはRuby 2.6.0からdefault gemになっています。

test-unitはRuby 2.2.0で再度標準添付されるようになってからbundled gemになっています。

そんなtest-unitのデータ駆動テスト機能をさらに便利にしたものがRuby 2.6.0に入っています。

データ駆動テスト

データ駆動テストとは同じテスト内容をいろいろなデータで実行するテスト方法です。パラメーター化テストと呼ばれることもあります。いろいろな入力に対するテストを簡潔に書きたいときに便利です。

test-unitでは結構前からデータ駆動テストをサポートしています。

たとえば、正の数同士の足し算と負の数同士の足し算をテストすることを考えます。データ駆動テスト機能を使わない場合は次のようにそれぞれのケースについてテストを作ります。

require "test-unit" class TestAdd < Test :: Unit :: TestCase def test_positive_positive assert_equal ( 3 , my_add ( 1 , 2 )) end def test_negative_negative assert_equal ( - 3 , my_add ( - 1 , - 2 )) end end

データ駆動テスト機能を使う場合はテストは1つで、テストに使うデータを複数書きます。

require "test-unit" class TestAdd < Test :: Unit :: TestCase data ( "positive + positive" , [ 3 , 1 , 2 ]) data ( "negative + negative" , [ - 3 , - 1 , - 2 ]) def test_add ( data ) expected , augend , addend = data assert_equal ( expected , my_add ( augend , addend )) end end

データが増えてくるほど、データ駆動テスト機能を使った方がテストを書きやすくなります。データを追加するだけで済むからです。ただ、読みやすさは従来のテストの方が上です。テストに使うデータがベタ書きされているからです。

test-unitのデータ駆動テスト機能をもっと知りたくなった人はRuby用単体テストフレームワークtest-unitでのデータ駆動テストの紹介を参照してください。

データ表生成機能

Ruby 2.6.0に入っているtest-unitではデータ駆動テストがさらに便利になっています。

まだなんと呼ぶのがよいか決めかねているのですが、今のところデータ表（data matrix）と呼んでいるものを生成する機能が入っています。

データ表というのは各テストで使うデータをまとめたものです。前述のテストの場合は次のようになります。 data を使う毎に1行増えます。

ラベル expected augend addend "positive + positive" 3 1 2 "negative + negative" -3 -1 -2

このデータ表をいい感じに生成する機能が入っています。

前述のテストで正の数と負の数を足す場合もテストしたくなったとします。その場合、従来のデータ駆動テスト機能の書き方では次のように書きます。 data を2つ増やしています。

require "test-unit" class TestAdd < Test :: Unit :: TestCase data ( "positive + positive" , [ 3 , 1 , 2 ]) data ( "negative + negative" , [ - 3 , - 1 , - 2 ]) data ( "positive + negative" , [ - 1 , 1 , - 2 ]) # 追加 data ( "negative + positive" , [ 1 , - 1 , 2 ]) # 追加 def test_add ( data ) expected , augend , addend = data assert_equal ( expected , my_add ( augend , addend )) end end

データ表は次のようになります。

ラベル expected augend addend "positive + positive" 3 1 2 "negative + negative" -3 -1 -2 "positive + negative" -1 1 -2 "negative + positive" 1 -1 2

データ表生成機能を使うと次のように書けます。 data の第一引数に Symbol を指定しているところがポイントです。テストに渡されるデータは Hash になっていてキーがシンボルで値が対象データです。

require "test-unit" class TestAddDataMatrix < Test :: Unit :: TestCase data ( :augend , [ 1 , - 1 ]) data ( :addend , [ 2 , - 2 ]) def test_add ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

これで次のデータ表を生成できます。

ラベル augend addend 備考 "addend: 2, augend: 1" 2 1 正+正 "addend: 2, augend: -1" 2 -1 正+負 "addend: -2, augend: 1" -2 1 負+正 "addend: -2, augend: -1" -2 -1 負+負

期待する結果（ expected ）は生成できないのでRuby組み込みの Integer#+ の結果を使っています。これは実は大事なポイントです。データ表生成機能を使えるのは次の場合だけです。

期待する結果がデータに依らず一意に定まる

データから期待する結果を計算できる

今回の場合は期待する結果を計算できるので使えました。

なお、期待する結果は必ずしも正しい結果を返すはずの既存の実装（今回の場合は Integer#+ ）を使わなくても大丈夫です。次のように「エンコードしてデコードしたら元に戻る」ようなときでもデータ表生成機能を使えます。これは性質をテストしているケースです。（性質をテストすることについてはここを参照してください、とか書いておきたいけど、どこがいいかしら。）

assert_equal ( raw_data , decode ( encode ( raw_data )))

この例ではパラメーターは augend と addend の2つでそれぞれに正と負があるので、4パターンでしたが、パラメーター数が増えたりバリエーションが増えると一気にパターンが増えます。そのときはこのデータ表生成機能が便利です。

なお、この機能はRed Chainer（Rubyだけで実装しているディープラーニングフレームワーク）で使うために作りました。もともとRed Chainerのテスト内でデータ表を生成していたのですがこの機能を使うことでだいぶスッキリしました。

データを使い回す

実はRed Chainerのテストをスッキリさせるためにはデータ表を生成するだけでは機能が足りませんでした。同じデータ表を複数のテストで共有する機能が必要でした。

前述の例で言うと、同じデータ表を足し算のテストでも引き算のテストでも使いたいという感じです。コードで言うと、以下をもっといい感じに書きたいということです。

require "test-unit" class TestCalc < Test :: Unit :: TestCase data ( :number1 , [ 1 , - 1 ]) data ( :number2 , [ 2 , - 2 ]) def test_add ( data ) number1 = data [ :number1 ] number2 = data [ :number2 ] assert_equal ( number1 + number2 , my_add ( number1 , number2 )) end data ( :number1 , [ 1 , - 1 ]) data ( :number2 , [ 2 , - 2 ]) def test_subtract ( data ) number1 = data [ :number1 ] number2 = data [ :number2 ] assert_equal ( number1 - number2 , my_subtract ( number1 , number2 )) end end

そこで、 data メソッドに keep: true オプションを追加しました。これで一度 data を書けば後続するテストでも同じデータを使うようになります。

require "test-unit" class TestCalc < Test :: Unit :: TestCase data ( :number1 , [ 1 , - 1 ], keep: true ) # keep: trueを追加 data ( :number2 , [ 2 , - 2 ], keep: true ) # keep: trueを追加 def test_add ( data ) number1 = data [ :number1 ] number2 = data [ :number2 ] assert_equal ( number1 + number2 , my_add ( number1 , number2 )) end # ここにdataはいらない def test_subtract ( data ) number1 = data [ :number1 ] number2 = data [ :number2 ] assert_equal ( number1 - number2 , my_subtract ( number1 , number2 )) end end

データ表を複数生成する

実はRed Chainerのテストをスッキリさせるためにはデータを使い回せても機能が足りませんでした。1つのテストに対して複数のデータ表を生成する機能が必要でした。

前述の例で言うと、小さい数同士と大きい数同士で別のデータ表を作りたい、ただし、小さい数と大きい数の組み合わせはいらないという感じです。（わかりにくい。）

データ表で言うと次の2つのデータ表を使う感じです。

小さい数用のデータ表：

内容 augend addend 小さい正 + 小さい正 2 1 小さい正 + 小さい負 2 -1 小さい負 + 小さい正 -2 1 小さい負 + 小さい負 -2 -1

大きい数用のデータ表：

内容 augend addend 大きい正 + 大きい正 20000 10000 大きい正 + 大きい負 20000 -10000 大きい負 + 大きい正 -20000 10000 大きい負 + 大きい負 -20000 -10000

コードで言うと、以下をもっといい感じに書きたいということです。

require "test-unit" class TestAdd < Test :: Unit :: TestCase data ( :augend , [ 1 , - 1 ]) data ( :addend , [ 2 , - 2 ]) def test_add_small ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end data ( :augend , [ 10000 , - 10000 ]) data ( :addend , [ 20000 , - 20000 ]) def test_add_large ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

そこで、 data メソッドに group: オプションを追加しました。同じグループ毎にデータ表を生成します。

require "test-unit" class TestAdd < Test :: Unit :: TestCase data ( :augend , [ 1 , - 1 ], group: :small ) # 小さい数用 data ( :addend , [ 2 , - 2 ], group: :small ) # 小さい数用 data ( :augend , [ 10000 , - 10000 ], group: :large ) # 大きい数用 data ( :addend , [ 20000 , - 20000 ], group: :large ) # 大きい数用 def test_add ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

setup でもデータを参照可能にする

実はRed Chainerのテストをスッキリさせるためにはデータ表を複数作れても機能が足りませんでした。テスト実行中にデータを参照しやすくする機能が必要でした。

従来のデータ駆動テスト機能ではテストメソッドの引数でデータを渡していました。そのため、 setup 中でデータを参照できませんでした。

require "test-unit" class TestAdd < Test :: Unit :: TestCase def setup # ここでデータを参照できない end data ( :augend , [ 1 , - 1 ]) data ( :addend , [ 2 , - 2 ]) def test_add ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

Red Chainerのテストではデータを前処理したかったので次のように明示的に前処理メソッドを呼んでいました。

require "test-unit" class TestAdd < Test :: Unit :: TestCase def my_setup ( data ) # 前処理 end data ( :augend , [ 1 , - 1 ]) data ( :addend , [ 2 , - 2 ]) def test_add ( data ) my_setup ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

これは微妙なので data でデータを参照できるようにしました。

require "test-unit" class TestAdd < Test :: Unit :: TestCase def setup p data # データを参照できる！ end data ( :augend , [ 1 , - 1 ]) data ( :addend , [ 2 , - 2 ]) def test_add ( data ) augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

また、テストでも引数でデータを受け取らなくてもよくなりました。（従来どおり受け取ってもよいです。）

require "test-unit" class TestAdd < Test :: Unit :: TestCase def setup p data # データを参照できる！ end data ( :augend , [ 1 , - 1 ]) data ( :addend , [ 2 , - 2 ]) def test_add # test_add(data)としなくてもよい！ augend = data [ :augend ] addend = data [ :addend ] assert_equal ( augend + addend , my_add ( augend , addend )) end end

まとめ

Red Chainerのためにtest-unitにデータ表生成機能を追加しました。Ruby 2.6.0にもこの機能を使えるtest-unitが入っています。ぜひ活用してください。

なお、Ruby 2.6.0でなくてもRubyGemsで新しいtest-unit（3.2.9以降）にアップグレードすれば使えます。Red Chainerでもそうやって使っています。

Red Chainerの開発に参加したい人はRed Data Toolsに参加してください。オンラインのチャットか東京で毎月開催している開発の集まり（次回は2018年1月22日）でどうやって進めていくか相談しましょう。