XP流に言えば...
Unit TestをサポートするRuby用のTesting Framework
Ruby流に言えば...
クラス単位、モジュール単位、メソッド単位のテストを 支援するためのRubyで書かれたRubyのライブラリ
作者は誰?
助田 雅紀 (協力者:石井 勝)
いつできたの?
1999年10月2日 デビュー
なぜ作ったの?
Javaにあって、Rubyに無いのが悔しかったから
向く分野は...
向かない分野は...
3行出力する単純なスクリプト
# foo.rb puts "Hello RubyUnit." puts "Hello PerlUnit." puts "Hello JavaUnit." Hello RubyUnit. Hello PerlUnit. Hello JavaUnit.
共通な部分をメソッドにすると ...
共通な部分をunitメソッドで取り出したスクリプト
# foo.rb def unit(lang) ??? end puts unit("Ruby") puts unit("Perl") puts unit("Java")
unitメソッドを作ってみよう。まずはテストから。
テストを書く = テストクラスを作る
# testfoo.rb require 'runit/testcase' class TestUnit < RUNIT::TestCase end
テスト本体のメソッドは、必ず "test" で始める
# testfoo.rb require 'runit/testcase' class TestUnit < RUNIT::TestCase def test_unit end end
unitメソッドを使えるようにfoo.rbをロードする
# testfoo.rb require 'runit/testcase' require 'foo' class TestUnit < RUNIT::TestCase def test_unit assert_equal("Hello RubyUnit.", unit("Ruby")) end end
TestRunnerを使った決り文句
# testfoo.rb require 'runit/testcase' require 'runit/cui/testrunner' require 'foo' class TestUnit < RUNIT::TestCase def test_unit assert_equal("Hello RubyUnit.", unit("Ruby")) end end RUNIT::CUI::TestRunner.run(TestUnit.suite)
foo.rbがロードされるときはメイン処理を実行しないようにする
# foo.rb def unit(lang) end if $0 == __FILE__ puts unit("Ruby") puts unit("Perl") puts unit("Java") end # testfoo.rb require 'foo' ...(snip)
テストが失敗することを確認するために実行する
%ruby testfoo.rb TestUnit#test_unit F. Time: 0.097835 FAILURES!!! Test Results: Run: 1/1(1 asserts) Failures: 1 Errors: 0 Failures: 1 testfoo.rb:7:in `test_unit'(TestUnit): expected:<Hello RubyUnit.> but was:<nil> (RUNIT::AssertionFailedError) from testfoo.rb:10
Failure
assert_equalが失敗したとき
assert_equal("Hello RubyUnit.", unit("Ruby")) # ==> RUNIT::AssertionFailedError (= Failure)
Error
assert_equal以外のところで予想しない例外が発生したとき
assert_equal("Hello RubyUnit.", unit) # ==> ArgumentError
合格を目指してunitメソッドを完成させる
#foo.rb def unit(lang) "Hello " + lang + "Unit." end if $0 == __FILE__ puts unit("Ruby") puts unit("Perl") puts unit("Java") end
再度テストを実行すると見事合格!!!
TestUnit#test_unit . Time: 0.085445 OK (1/1 tests 1 asserts)
def unit(lang) puts "Hello " + lang + "Unit." end if $0 == __FILE__ unit("Ruby") unit("Perl") unit("Java") end
assert_equalを使ったunitメソッドのテストは???
Javaのときだけ "Hello JUnit."と出力するようにunitを変更
その前にTestUnitを変更
class TestUnit < RUNIT::TestCase def test_unit assert_equal("Hello RubyUnit.", unit("Ruby")) end end
さて、どう書き換える?
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.>
assert_equalを追加する
テストメソッドを追加する
lang が Java のときの処理を追加
def unit(lang) if lang == "Java" return "Hello JUnit." end "Hello " + lang + "Unit." end
今度は合格
TestUnit#test_unit . Time: 0.062706 OK (1/1 tests 2 asserts)
よりわかりやすいコードに変更する(リファクタリング)
テストは修正しない
def unit(lang) flamework = lang + "Unit" flamework = "JUnit" if lang == "Java" "Hello #{flamework}." end
assert_equal(expected, actual) と assert(expected == actual) とでは テストは同じだが、失敗したときの情報量が違う。
x = 2 assert_equal(10, x + 7) # ==> expected:<10> but was:<9> x = 2 assert(10 == x + 7) # ==> condition is <false:FalseClass>
Directory.new(path)
Directoryオブジェクトを生成する
# directory.rb class Directory def initialize(path) @path = path end end
Directory#files
Directory#filesのテストメソッドTestDirectory#test_files
# testdirectory.rb require "runit/testcase" require "runit/cui/testrunner" require "directory" class TestDirectory < RUNIT::TestCase def test_files end end RUNIT::CUI::TestRunner.run(TestDirectory.suite)
テスト用のディレクトリとファイルの生成
# testdirectory.rb class TestDirectory < RUNIT::TestCase def create_file(filename) ... # create file end def test_files Dir.mkdir("testdir") create_file("testdir/file01") create_file("testdir/file02") create_file("testdir/file03") 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") 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 remove_dir(dir) # delete directory and files end 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) remove_dir("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") begin dir = Directory.new("testdir") exp = ["file01", "file02", "file03"] assert_equal(exp, dir.files) ensure remove_dir("testdir") end end end
確認できたらテストに合格するようにfilesメソッドを定義する
# directory.rb class Directory def initialize(path) @path = path end def files Dir[@path + "/*"].collect {|path| File.basename(path) } end end
TestDirectoryにtest_files_emptyメソッドを追加
# testdirectory.rb class TestDirectory < RUNIT::TestCase def test_files_empty Dir.mkdir("testdir") begin dir = Directory.new("testdir") exp = [] assert_equal(exp, dir.files) ensure remove_dir("testdir") end end end
テストの準備
テスト用ディレクトリ testdir の作成
Dir.mkdir("testdir")
Directoryオブジェクトの生成
dir = Directory.new("testdir")
テストの後始末
テストのためのディレクトリ testdir の削除
remove_dir("testdir")
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
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
実行順序
setup ==> test_files ==> teardown ==> setup ==> test_files_empty ==> teardown
test_filesとtest_files_emptyをよくよく眺めてみれば...
class TestDirectory < RUNIT::TestCase ...(snip) 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
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
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("err_path") # ArgumentError Directory.new("normal_path") # OK
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)
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)
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
ダミーのクラスを作ってモジュールの各メソッドを呼び出す
module ModuleToTest def a_method ...(snip) end end class Dummy include ModuleToTest end class TestModuleToTest < RUNIT::TestCase def test_a_method dummy = Dummy.new exp = ... assert_equal(exp, dummy.a_method) end end
テストは必要ない
テストは必要
各テストスクリプトの実行部分を
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
# 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)
# 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 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