[2021.4] RubyUnit (RUNIT) は廃れました。Ruby 3.0 では 'test-unit' パッケージ (Test::Unit
モジュール) または 'minitest' パッケージを使ってください。
Rubyスクリプトを効果的にテストする「RubyUnit」を試してみる。
Rubyは書きやすいし,高機能なので短いスクリプトで狙ったものが書ける。だから楽しい。だからかもしれないけど,動いてるスクリプトでもリファクタリング(Refactoring)することが多いように思う。
で,うっかり振る舞いを変えてしまったりすることがある。Rubyは何でもかんでも実行時に解決するので,実行してみないと発見が難しい。
で,テストフレームワーク(Testing Framework)の出番。Rubyスクリプトの各パーツの挙動を調べるテスト・スクリプトを用意し,そのテストに合格しないといけない,ということにすれば,安心して内部を触ることができる。
簡単なスクリプトを書いてみる。
5: class Account 6: def initialize 7: @balance = 0 8: end 9: def deposit(amount) 10: @balance += amount 11: end 12: def withdraw(amount) 13: @balance -= amount 14: end 15: attr_reader :balance 16: end 17: 18: if __FILE__ == $0 19: a = Account.new 20: a.deposit 100 21: a.withdraw 50 22: puts a.balance 23: end
Accountは口座を模したクラス。depositメソッドで預けてwithdrawメソッドで引き出す。balanceメソッドでその時点の残高を得る。
実行結果 50
このスクリプトをテストするテストスクリプトを書いてみる。
4: require "runit/testcase" 5: require "runit/cui/testrunner" 6: 7: require "sample-1.1.rb" 8: 9: class AccountTest < RUNIT::TestCase 10: def test1 11: a = Account.new 12: a.deposit 100 13: a.withdraw 30 14: assert_equal(70, a.balance) 15: end 16: 17: def test2 18: a = Account.new 19: a.deposit 50 20: a.withdraw 80 21: assert_equal(-30, a.balance) 22: end 23: end 24: 25: RUNIT::CUI::TestRunner.run(AccountTest.suite)
テストは,RUNIT::TestCaseクラスから派生したテストクラスに記述する。メソッド名がtestで始まるメソッドが名前の順番に呼ばれる。
テストクラスでは,assert_equalメソッドなどでテストを行う。test1, test2でそれぞれ,残高が期待する値になるかをテストしている。
テストの実行は,RUNIT::CUI::TestRunnerを用いる。
実行結果; AccountTest#test1 . AccountTest#test2 . Time: 0.014404 OK (2/2 tests 2 asserts)
正しくテストにパスした。
このAccountクラスの動作を変更し,マイナスの預け入れや残高がマイナスとなるような引き出しができないようにしてみる。
先にテストクラスを修正する。
4: require "runit/testcase" 5: require "runit/cui/testrunner" 6: 7: require "sample-1.1.rb" 8: 9: class AccountTest < RUNIT::TestCase 10: def test1 11: a = Account.new 12: a.deposit 100 13: a.withdraw 30 14: assert_equal(70, a.balance) 15: end 16: 17: def test2 18: a = Account.new 19: a.deposit 50 20: assert_exception(RuntimeError) { 21: a.withdraw 80 22: } 23: assert_equal(50, a.balance) 24: end 25: 26: def test3 27: a = Account.new 28: a.deposit 100 29: assert_exception(ArgumentError) { 30: a.deposit -50 31: } 32: assert_equal(100, a.balance) 33: end 34: end 35: 36: RUNIT::CUI::TestRunner.run(AccountTest.suite)
テスト項目として,残高を超えた引き出しとマイナスの預け入れを入れた。例外が発生するかをテストするときは,assert_exceptionメソッドを使う。
実行結果; AccountTest#test1 . AccountTest#test2 F. AccountTest#test3 F. Time: 0.024356 FAILURES!!! Test Results: Run: 3/3(3 asserts) Failures: 2 Errors: 0 Failures: 2 sample-1.2-test.rb:20:in `test2'(AccountTest): expected:<RuntimeError> but was:<NO EXCEPTION RAISED> (RUNIT::AssertionFailedError) from sample-1.2-test.rb:36 sample-1.2-test.rb:29:in `test3'(AccountTest): expected:<ArgumentError> but was:<NO EXCEPTION RAISED> (RUNIT::AssertionFailedError) from sample-1.2-test.rb:36
例外が発生しなかったためテストに通らなかった。このテストに通るようにAccountクラスを修正する。
5: class Account 6: def initialize 7: @balance = 0 8: end 9: def deposit(amount) 10: if amount >= 0 11: @balance += amount 12: else 13: fail ArgumentError 14: end 15: end 16: def withdraw(amount) 17: if @balance >= amount 18: @balance -= amount 19: else 20: fail RuntimeError, "balance shortage" 21: end 22: end 23: attr_reader :balance 24: end
再びテストしてみます。
実行結果; AccountTest#test1 . AccountTest#test2 . AccountTest#test3 . Time: 0.023123 OK (3/3 tests 5 asserts)
今度は通りました。このように,テストを先に修正するようにすると,安全に手を入れることができます。同時に,実際の使われ方も分かるため,設計もスムーズに進めることができます。