(Test::Unit)チュートリアル
下準備
まず,作成するプログラムのためのディレクトリを作ります.
gcm/ -+- lib | +- test -- run-test.rb
libというディレクトリの下にソースファイルを置き,testというディレクトリの下に,run-test.rbとテストファイルを置きます.
次に,テストを実行するファイルrun-test.rbを作成します. run-test.rbの中身は以下のようにします.
#!/usr/bin/env ruby require 'test/unit' test_file = "test/test_*.rb" $:.unshift(File.join(File.expand_path("."), "lib")) $:.unshift(File.join(File.expand_path("."), "test")) Dir.glob(test_file) do |file| require file.sub(/\.rb$/, '') end
run-test.rbに実行権を付けます.
% chmod +x test/run-test.rb
以上でテストする環境が整いました.
お題: 最大公約数(Greatest Common Measure)
2つの数に対し,割り切れる最大の数を求めるというプログラムを作ってみたいと思います.ファイル名は英語の頭文字をとることにします.ソースファイルはlib/gcm.rb,テストファイルはtest/test_gcm.rbにします.
テストファイルの作成
まずテストファイルtest/test_gcm.rbから作成します.test/test_gcm.rbを開き,以下のように記述します.
require 'gcm.rb'
これは,テストしたいソースファイルを読み込んでいます.実際にテストしてみましょう.
C-cT
このコマンドは,テストを行い,その結果を新しいフレームに表示するというものです.1度新しいフレームが出たら,次からのテストは今出たフレームを使用したいので,2度目のテストからは以下を実行するとよいでしょう.
C-cC-t
このコマンドでは,新しいフレームを作らずにテストを行うことができます.
では,エラーメッセージに注目してみます.
./test/run-test.rb ./test/test_gcm.rb:2:in `require': No such file to load -- gcm (LoadError) from ./test/test_gcm.rb:2 from ./test/run-test.rb:13:in `require' from ./test/run-test.rb:13 from ./test/run-test.rb:12:in `glob' from ./test/run-test.rb:12 run-test exited abnormally with code 1 at Sun Sep 5 17:45:41
どうやら,gcm.rbというファイルがないと言っているようです.作っていないので当然です.では,作って見ましょう.
% touch lib/gcm.rb
そしてもう一度テストしてみます.すると先ほどのエラーがなくなりました.ここまでは,ミスを犯していないようです.
テストの作成
1と2の最大公約数は1
ではテストを作成していきましょう.1と2の最大公約数は1になるはずです.このテストを書いてみます.
require 'gcm' class TestGreatestCommonMeasure < Test::Unit::TestCase def test_gcm assert_equal(1, gcd(1, 2)) end end
3行目にclassと書いてある行があります.ここには全体のテストの名前を書きます.今回は最大公約数のテストなので,TestGreatestCommonMeasureとしています.
次にdefとendに囲まれた部分があります.ここに実際のテストを書きます.
assert_equalは二つのものを用いてテストを行います.それをassert_equal(1番目,2番目)と書きます.1番目の欄には,"こうなって欲しい"という値を書きます.2番目の欄ははテストしたい事を書きます.今回の場合は,"gcd(1, 2)を実行したら1になって欲しい"ということになります.
全体の構成を作るための分割した処理ごとにdefとendで囲まれる部分を作るとよいでしょう.そして,分割した処理のテスト毎にも適した名前をつけてあげます.
ではC-cC-tでテストしてみましょう.すると結果は次のようになりました.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started E Finished in 0.00095 seconds. 1) Error: test_gcm(TestGreatestCommonMeasure): NoMethodError: undefined method `gcm' for #<TestGreatestCommonMeasure:0x813d6ec> ./test/test_gcm.rb:7:in `test_gcm' 1 tests, 0 assertions, 0 failures, 1 errors run-test exited abnormally with code 1 at Sun Sep 5 18:30:28
これは,gcmというメソッドが定義されていないというエラーです.
エラーが起こっている状態はいい状態ではありません.早くエラーを除去し,安心してプログラムを進めたいものです.
ここではgcmが定義されていないと言われているので,lib/gcm.rbにgcmの定義を書きます.
def gcm(num1, num2) end
これでもう一度C-cC-tでテストしてみます.次のように表示されました.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started F Finished in 0.035519 seconds. 1) Failure: test_gcm(TestGreatestCommonMeasure) [./test/test_gcm.rb:7]: <1> expected but was <nil>. 1 tests, 1 assertions, 1 failures, 0 errors run-test exited abnormally with code 1 at Sun Sep 5 18:37:02
これはエラーではありません.失敗です.メッセージを読んでみると,下の方に"<1> expected but was <nil>"と書いてあります.テストプログラムを思い出すと,"1になって欲しい"というテストを書きました.ですが結果はnil(定義されていないことを示す値)になっているようです.
私達は,まずテストを通すことを考えます.ここでは結果が1になればテストが通りそうです.よって,lib/gcm.rbを1を返すように変更します.
def gcm(num1, num2) 1 end
変更を加えたのでテストします.C-cC-tでテストを実行した結果は次のようになりました.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started . Finished in 0.000637 seconds. 1 tests, 1 assertions, 0 failures, 0 errors run-test finished at Sun Sep 5 18:41:54
成功しました!作ったプログラムは期待したとおりに動きました.これはとても素晴らしいことです.休憩してお茶でも飲んでお祝いしましょう.
主問題を部分問題へ
テストは通りました.でもこれだけでは最大公約数を求めるシステムが出来上がったか少し怪しいです.新しくテストを追加してみましょう.
def test_gcm assert_equal(1, gcm(1, 2)) assert_equal(2, gcm(2, 4)) end
2と4の最大公約数は2になって欲しいです.ではC-cC-tでテストを実行してみます.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started F Finished in 0.031784 seconds. 1) Failure: test_gcm(TestGreatestCommonMeasure) [./test/test_gcm.rb:8]: <2> expected but was <1>. 1 tests, 2 assertions, 1 failures, 0 errors run-test exited abnormally with code 1 at Sun Sep 5 21:51:09
2番目のテストで,"2になって欲しいのに,1になった"と言われています.このテストを通すためには,やはりきちんとした最大公約数を求めるプログラムを書かなければならないようです.
ここで,最大公約数を求めるに当たってしなければならないことを抽出します.
- 2つの数それぞれの割り切れる数を求める
- 2つの数どちらでも割り切れる数を求める
- その中の最大のものを求める
最大公約数を求めるという問題を,3つの小さな問題に分けることができました.問題がシンプルになり,1つ1つを頭で考えやすくなったと思います.
この部分問題を,焦らず,一つ一つクリアしていきます.つまり,それぞれの問題についてテストを作り,問題を解決していくということです.
与えられた数の割り切れる数を求める
まずは与えられた数に対して,割り切れる数を求める問題に注目してみます.
さっそくテストを書いてみます.ここでは,"割り切ることができる"という意味で,dividableという名前にします.
def test_dividable assert_equal([1], dividable(1)) end
1を割り切れる数は1であるというテストを書きました.
ここで,"dividable(1)を実行すると[1]になって欲しい"というテストを書きました.[1]とは何かというと,"複数の要素を持つまとまり"と考えてください.例えば,8を割り切ることができる数は,1,2,4,8の4つです.これを一つのまとまりとして扱いたい時に,[1,2,4,8]という記述を使うことができます.
せっかく例を考えたので,これもテストに追加しましょう.
def test_dividable assert_equal([1], dividable(1)) assert_equal([1,2,4,8], dividable(8)) end
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started EF Finished in 0.042336 seconds. 1) Error: test_dividable(TestGreatestCommonMeasure): NoMethodError: undefined method `dividable' for #<TestGreatestCommonMeasure:0x813b390> ./test/test_gcm.rb:18:in `test_dividable' 2) Failure: test_gcm(TestGreatestCommonMeasure) [./test/test_gcm.rb:8]: <2> expected but was <1>. 2 tests, 2 assertions, 1 failures, 1 errors run-test exited abnormally with code 1 at Mon Sep 6 09:20:03
先ほどの失敗に加えて,新たに"dividable"が定義されていないというエラーが出ました.
今注目している問題は,"ある数の割り切れる数を求める"こと(テスト結果の1番目のエラーの箇所)です.しかし,2番目の失敗(最大公約数のテスト)は,テストを実行する度に失敗と言われます.1つの問題に集中するために,最大公約数のテストは一時テストしないようにしましょう.
def _test_gcm assert_equal(1, gcm(1, 2)) assert_equal(2, gcm(2, 4)) end
テストプログラムでは次のような決まりがあります.
- "test_"で始まる定義(def〜endのまとまり)のテストを行う
- テストのクラス(class〜endのまとまり)では1つ以上"test_"で始まる定義がなければならない.
この始めの規則により,"def test_gcm"を"def _test_gcm"に変更したので,test_gcmのテストは行われなくなりました.
もう一度テストしてみましょう.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started E Finished in 0.000917 seconds. 1) Error: test_dividable(TestGreatestCommonMeasure): NoMethodError: undefined method `dividable' for #<TestGreatestCommonMeasure:0x813b3b8> ./test/test_gcm.rb:18:in `test_dividable' 1 tests, 0 assertions, 0 failures, 1 errors run-test exited abnormally with code 1 at Mon Sep 6 11:22:54
エラーが1つだけになりました.この1つのエラーに集中して作業を進めましょう.
エラーはdividableが定義されていないと言っていますので,ソースファイルにdividableを定義します.
def dividable(number) end
dividableは1つの数に対して処理をするので,数を受け取れるように(number)という変数を定義しています.変更を加えたので,テストしてみます.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started F Finished in 0.03121 seconds. 1) Failure: test_dividable(TestGreatestCommonMeasure) [./test/test_gcm.rb:18]: <[1]> expected but was <nil>. 1 tests, 1 assertions, 1 failures, 0 errors run-test exited abnormally with code 1 at Mon Sep 6 11:26:57
エラーがなくなりましたが,テストは失敗したようです.ここで[1]を期待しているので,[1]を返すように変更し,テストします.すると,test_dividableの2番目のテストで,"<[1,2,4,8]> expected but was <[1]>"という結果になります.このテストもパスするようにソースファイルを書いていきます.
def dividable(number) dividable_numbers = [] for x in 1..number dividable_numbers << x if (number % x).zero? end dividable_numbers end
これは何をしているかというと,
- ある数numberを受け取る
- 1からnumberまでの数で,numberを割る
- 余りが0ならばdividable_numbersというまとまりに追加
- dividable_numbersを返す
という流れです.簡単に言うと,割り切れる数のまとまりを作ったということです.
きちんと動作するかテストしてみましょう.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started . Finished in 0.006604 seconds. 1 tests, 4 assertions, 0 failures, 0 errors run-test finished at Mon Sep 6 11:39:14
成功しました!部分問題のうち1つが解決したようです.休憩してお祝いしてください!
2つの数どちらでも割り切れる数を求める
休憩してリフレッシュした頭で,2つめの部分問題にとりかかりましょう.
ある数に対して割り切れる数のまとまりを作成することはできるようになりました.次は,2つの数があり,どちらの数でも割り切れる数が知りたいという目標があります.
例を挙げるとこうなります.
- 4で割り切れる数は[1,2,4]
- 8で割り切れる数は[1,2,4,8]
- 4でも8でも割り切れる数は[1,2,4]
これは違う表現をすると,"2つのまとまりがあり,その共通部分のまとまりを作成する"ということになります.
ここで,1つ思い出したことがあります.Rubyには自分で作らなくてもたくさんの機能が始めから備わっています.今解こうとしている問題の,"2つのまとまりから,共通部分のまとまりを得る"という機能も備わっています.
ですから,この機能を使うことにします. この機能は"&"を使います.2つのまとまりをそれぞれa,bとすると
a & b
で2つのまとまりの共通部分を求めることができます. これを使えば,2つめの問題の"2つの数どちらでも割り切れる数を求める"が実現できそうです.
つまり,2つの数それぞれに対して,"dividable"でそれぞれの割り切れる数を求め,"&"でその共通部分を求めます. 例えば,4と8どちらでも割り切れる数を求めるには,
dividable(4) & dividable(8)
を使うとよさそうです. これをテストにしてみましょう.
def test_intersection assert_equal([1,2,4], dividable(4) & dividable(8)) end
テストを実行してみます.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started .. Finished in 0.000868 seconds. 2 tests, 3 assertions, 0 failures, 0 errors run-test finished at Mon Sep 6 11:57:07
成功しました!これで"2つの数どちらでも割り切れる数を求める"という問題は解決しました!一区切りついたので,少し休憩しましょう.
2つの数を割り切れるまとまりの中の最大の数を求める
最後の部分問題に取りかかりましょう!
2つの数を割り切れるまとまりを作成することはできました.今回はこのまとまりの中の最大の数を求めてみます.
これも違う言いかたをするとわかりやすくなりそうです."あるまとまりの中の最大の数を求める"と言い替えることができます. これもよく使われそうな機能ですね.この機能も備わっているかrefeなどで調べてみましょう
ありました!maxという命令が見つかりました.まとまりに対し,maxという命令を送ると,まとまりの最大の数を返してくれる機能のようです.
どのような動作をするかテストで確認してみましょう.
def test_array_max assert_equal(3, [1, 3, 2].max) end
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started ... Finished in 0.000973 seconds. 3 tests, 4 assertions, 0 failures, 0 errors run-test finished at Mon Sep 6 12:09:39
成功しました!Rubyには多くの機能があることを実感します.
部品をまとめる
これまでに
- ある数を割り切れる数のまとまりを求める
- 2つの数の割り切れるまとまりを求める
- まとまりの最大の数を求める
という3つの部品を作ってきました.この部品を組み合わせて,最大公約数を求めるという目標を達成しましょう!
test_gcmはテストされないようにしていました.これをテストされるように変更しなおします.
def test_gcm assert_equal(1, gcm(1, 2)) assert_equal(2, gcm(2, 4)) end
変更を加えたのでテストしてみます.
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started ..F. Finished in 0.037895 seconds. 1) Failure: test_gcm(TestGreatestCommonMeasure) [./test/test_gcm.rb:8]: <2> expected but was <1>. 4 tests, 6 assertions, 1 failures, 0 errors run-test exited abnormally with code 1 at Mon Sep 6 12:14:34
前と同じ失敗になりました.テストを通すために,部品を使って最大公約数を実現しましょう.
def gcm(num1, num2) (dividable(num1) & dividable(num2)).max end
これはnum1とnum2のそれぞれの割り切れる数を求め,"&"によって共通部分を抽出し,maxという命令で最大の数を求めています.
ではテストしてみましょう!
cd ~/work/ruby/gcm/ ./test/run-test.rb Loaded suite ./test/run-test Started .... Finished in 0.001161 seconds. 4 tests, 6 assertions, 0 failures, 0 errors run-test finished at Mon Sep 6 12:16:33
成功しました!みごとに最大公約数が求められているようです!最大公約数を求めるテストを追加しても全てパスします.
これで,最大公約数を求めるテストをパスするプログラムはできあがりました.後はリファクタリングをするだけです.
でもその前に盛大にお祝いしましょう!
キーワード:
参照:[Test::Unit]