Test::Unitでテストを書く

  • 337
    いいね
  • 7
    コメント
この記事は最終更新日から1年以上が経過しています。

RubyだとMiniTest(Test::Unit)が標準に入ってるけど機能が足りない感があり,RSpecもどうしたもんかなぁと思っていたんですが,gemでリリースされているTest::Unitが個人的には必要な機能が揃っていたので,今後はこれを中心にテストを書きたい.
ただ,Test::Unitのサイトのドキュメントはかなり簡素で毎回ソース読むはめになっているので,備忘録的に使い方をメモしておく.対象は最新リリースのv3.0.2.

インストール

gemで入れるだけ

$ gem install test-unit

テストの書き方

基本

今までのTest::Unitと変わらないので,classで書く.ただ,昔のTest::Unitとは違い,TestCase毎に呼ばれるstartupshutdownなどが増えている.

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

アサーション

https://test-unit.github.io/test-unit/ja/Test/Unit/Assertions.html

のページの"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が入ってくれれば嬉しいという所かな.

何か他にも良い機能があったら追記するかもしれないし,しないかもしれない.