RubyUnit Part2 : RubyUnit のススメ

本日のおしながき


RubyUnitって何?


誰が、いつ、なぜ、作ったの?


RubyUnitって何に使えるの?


RubyUnitを使ったテスト


テストの書き方

  1. TestCaseを派生させてテスト用のクラスを宣言する
  2. テストとして実行する内容をメソッドとして記述する
  3. TestRunnerクラスを使ってテストの実行処理を記述する

単純なスクリプト


メソッドのあるスクリプト


1. テストクラスの宣言


2. テストメソッドの記述(1)


2. テストメソッドの記述(2)


3. テストの実行処理


foo.rb側の準備


テストの実行


Failure と Error


合格を目指して


テストを考えてメソッドを定義

def unit(lang)
  puts "Hello " + lang + "Unit."
end
if $0 == __FILE__ 
  unit("Ruby")
  unit("Perl")
  unit("Java")
end

assert_equalを使ったunitメソッドのテストは???


unit メソッドの変更


assert_equalを追加する方法

class TestUnit < RUNIT::TestCase
  def test_unit
    assert_equal("Hello RubyUnit.", unit("Ruby"))
    assert_equal("Hello JUnit.", unit("Java"))
  end
end

# testfoo.rb の実行結果(抜粋)
TestUnit#test_unit F.
 ...(snip)
Run: 1/1(2 asserts) Failures: 1 Errors: 0
testfoo.rb:8:in `test_unit'(TestUnit): 
expected:<Hello JUnit.> 
but was:<Hello JavaUnit.>

テストメソッドを追加する方法

class TestUnit < RUNIT::TestCase
  def test_unit
    assert_equal("Hello RubyUnit.", unit("Ruby"))
  end
  def test_unit_Java
    assert_equal("Hello JUnit.", unit("Java"))
  end
end

# testfoo.rbの実行結果(抜粋)
TestUnit#test_unit .
TestUnit#test_unit_Java F.
 ...(snip)
Run: 2/2(2 asserts) Failures: 1 Errors: 0
testfoo.rb:10:in `test_unit_Java'(TestUnit): 
expected:<Hello JUnit.> 
but was:<Hello JavaUnit.>

2つの方法の違い


unitメソッドの変更


unitメソッドをさらに変更


Assert メソッド群(一部抜粋)


assert を使うのは最後


Directoryクラスの開発とテスト


Directory#files のテスト


テストの準備


テスト対象のオブジェクトの生成

# testdirectory.rb
class TestDirectory < RUNIT::TestCase
  def test_files
    Dir.mkdir("testdir")
    create_file("testdir/file01")
    create_file("testdir/file02")
    create_file("testdir/file03")
    dir = Directory.new("testdir")
  end
end

テスト本体の記述

# testdirectory.rb
class TestDirectory < RUNIT::TestCase
  def test_files
    Dir.mkdir("testdir")
    create_file("testdir/file01")
    create_file("testdir/file02")
    create_file("testdir/file03")
    dir = Directory.new("testdir")
    exp = ["file01", "file02", "file03"]
    assert_equal(exp, dir.files)
  end
end

テストの後始末


何があろうと後始末を必ず実行

# testdirectory.rb
class TestDirectory < RUNIT::TestCase
  def test_files
    Dir.mkdir("testdir")
    create_file("testdir/file01")
    create_file("testdir/file02")
    create_file("testdir/file03")
    begin
      dir = Directory.new("testdir")
      exp = ["file01", "file02", "file03"]
      assert_equal(exp, dir.files)
    ensure
      remove_dir("testdir")
    end
  end
end

Directory#filesの実装


ディレクトリが空のときのテスト


test_files と test_files_empty の共通点


テストの準備をsetupにまとめる

class TestDirectory < RUNIT::TestCase
  def setup
    Dir.mkdir("testdir")
    @dir = Directory.new("testdir")
  end
  def test_files
    ... (snip)
    assert_equal(exp, @dir.files)
    ... (snip)
  end
  def test_files_empty
    ... (snip)
    assert_equal(exp, @dir.files)
    ... (snip)
  end
end

テストの後始末をteardownにまとめる

class TestDirectory < RUNIT::TestCase
  def setup
    Dir.mkdir("testdir")
    @dir = Directory.new("testdir")
  end
  def teardown
    remove_dir("testdir")
  end
  def test_files
    create_file("testdir/file01")
    create_file("testdir/file02")
    create_file("testdir/file03")
    exp = ["file01", "file02", "file03"]
    assert_equal(exp, @dir.files)
  end
  def test_files_empty
    exp = []
    assert_equal(exp, @dir.files)
  end
end

setup と teardown

テストの共通部分をまとめる(1)


テストの共通部分をまとめる(2)

class TestDirectory < RUNIT::TestCase
  ...(snip)
  def test_files
    exp = ["file01", "file02", "file03"]
    exp.each do |file|
      create_file("testdir/" + file)
    end
    assert_equal(exp, @dir.files)
  end
  def test_files_empty
    exp = []
    exp.each do |file|
      create_file("testdir/" + file)
    end
    assert_equal(exp, @dir.files)
  end
end

テストの共通部分をまとめる(3)

class TestDirectory < RUNIT::TestCase
  ...(snip)
  def check_files(files)
    files.each do |file|
      create_file("testdir/" + file)
    end
    assert_equal(files, @dir.files)
  end
  def test_files
    exp = ["file01", "file02", "file03"]
    check_files(exp)
  end
  def test_files_empty
    exp = []
    check_files(exp)
  end
end

Directory.new の改訂


Directory.new のテストの追加

class TestDirectory < RUNIT::TestCase
  def test_s_new
    assert_exception(ArgumentError) {
      dir = Directory.new("err_path")
    }
  end
  def test_files
  ... (snip)
end

# テストの実行結果(抜粋)
`test_s_new'(TestDirectory): 
expected:<ArgumentError> 
but was:<NO EXCEPTION RAISED>
(RUNIT::AssertionFailedError)

Directory#initialize の実装

class Directory
  def initialize(path)
    if !File.exist?(path)
      raise ArgumentError
    end
    @path = path
  end
  def files
    Dir[@path + "/*"].collect {|path|
      File.basename(path)
    }
  end
end

例外にメッセージをつけてみる

class TestDirectory < RUNIT::TestCase
  def test_s_new
    err = assert_exception(ArgumentError) {
            dir = Directory.new("err_path")
          }
    exp = "Not Found:`err_path'"
    assert_equal(exp, err.message)
  end
  def test_files
  ... (snip)
end

# テストの実行結果(抜粋)
`test_s_new'(TestDirectory): 
expected:<Not Found:`err_path'> 
but was:<ArgumentError>
(RUNIT::AssertionFailedError)

Directory#initialize の修正

class Directory
  def initialize(path)
    if !File.exist?(path)
      raise ArgumentError, "Not Found:`#{path}'"
    end
    @path = path
  end
  def files
    Dir[@path + "/*"].collect {|path|
      File.basename(path)
    }
  end
end

モジュールのテスト


private メソッドのテスト


複数のテストをまとめて実行するための準備

各テストスクリプトの実行部分を

if $0 == __FILE__  〜 end 

で囲っておく

# testfoo.rb
if $0 == __FILE__
  RUNIT::CUI::TestRunner.run(TestUnit.suite)
end

# testdirectory.rb
if $0 == __FILE__
  RUNIT::CUI::TestRunner.run(TestDirectory.suite)
end

複数のテストをまとめて実行する(例1)

# testall.rb
require 'runit/testsuite'
require 'runit/cui/testrunner'

require 'testfoo'
require 'testdirectory'

suite = RUNIT::TestSuite.new

suite.add_test(TestUnit.suite)
suite.add_test(TestDirectory.suite)

RUNIT::CUI::TestRunner.run(suite)

複数のテストをまとめて実行する(例2)

# testall.rb
require 'runit/testsuite'
require 'runit/cui/testrunner'

require 'testfoo'
require 'testdirectory'

suite = RUNIT::TestSuite.new

ObjectSpace.each_object(Class) do |obj|
  if obj.superclass == RUNIT::TestCase
    suite.add_test(obj.suite)
  end
end

RUNIT::CUI::TestRunner.run(suite)

実行例

%ruby testall.rb
TestDirectory#test_files .
TestDirectory#test_files_empty .
TestDirectory#test_s_new .
TestUnit#test_unit .
Time: 0.49818
OK (4/4 tests  6 asserts)

特定のテストメソッドだけ実行する

class TestDirectory < RUNIT::TestCase
  def test_files
  end
  def test_files_empty
  end
  def test_s_new
  end
end

if ARGV.size == 0
  suite = TestDirectory.suite
else
  suite = RUNIT::TestSuite.new
  ARGV.each do |m|
    suite.add_test(TestDirectory.new(m))
  end
end

RUNIT::CUI::TestRunner.run(suite)

実行例

%ruby testdirectory.rb

TestDirectory#test_files .
TestDirectory#test_files_empty .
TestDirectory#test_s_new .
Time: 0.357167
OK (3/3 tests  4 asserts)


%ruby testdirectory.rb test_files_empty

TestDirectory#test_files_empty .
Time: 0.129801
OK (1/1 tests  1 asserts)

c2t.rb テストスクリプトのひな型を生成

%c2t.rb Foo
require 'runit/testcase'
require 'runit/cui/testrunner'

class TestFoo < RUNIT::TestCase

end

if $0 == __FILE__
  if ARGV.size == 0
    suite = TestFoo.suite
  else
    suite = RUNIT::TestSuite.new
    ARGV.each do |testmethod|
      suite.add_test(TestFoo.new(testmethod))
    end
  end
  RUNIT::CUI::TestRunner.run(suite)
end

今後