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