LoginSignup
369
363

More than 5 years have passed since last update.

Test::Unitでテストを書く

Last updated at Posted at 2014-10-15

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

アサーション

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

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

369
363
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
369
363