プログラムファイルにスペックを組み込む
「Rubyベストプラクティス」(p.30, 1.5.1 ライブラリファイルにテストを組み込む)に、プログラムが小さいときにプログラム本体とテストのコードを同居させる話が載ってた。いつもの「if $0 == __FILE__」節にテストコードを(「require 'test/unit'」と共に)書いておくというものだった。もともと Test::Unit (MiniTest::Unit) は rubyスクリプトとして実行するものだったからそれで良いけど、RSpec ではどうしたら良いんだろう。それにコマンドラインからの実行がテストだけになるのもちょっとね。
大体スペックのコードは Ruby互換ではあるが specコマンドでの実行を前提にしていて rubyで実行しようとしてもそんなメソッドないとかエラーになる。何か require しようにももともとそういうものじゃない。どうしよう。
というわけでもともとのプログラムファイルの構成をこんな感じにする。
<プログラム本体> if $0 == __FILE__ then <コマンドライン実行時の動作> end # if $0 == __FILE__ __END__ <スペック>
こうすると ruby コマンドでの実行時は __END__行以降は無視してくれるので大丈夫、__END__行以降にプログラム用のデータを置いとくとか出来なくなるけどそれはまあ。
で、スペック実行時は __END__ を削除して実行すれば
ruby -rtempfile -e 'Tempfile.open(File.basename((r = ARGV.shift), "rb")){|t| File.read(r).sub("__END__","").display t;t.close false;`spec #{t.path} #{ARGV.join(" ")}`.display}' <ファイル名> <specオプション>
削除したものを一度テンポラリファイルに書き出し、所要のオプションを付けて specコマンドで実行するワンライナー。こうすると「if $0 == __FILE__」節には普通にコマンドライン実行時の動作を書ける。
どんなもんでしょう。
ローマ数字
下記を書く時に考えた。ちょっと時間が経ってるけど「日本Rubyの会 公式Wiki - 第42回 Ruby/Rails勉強会@関西」(第42回 Ruby/Rails勉強会@関西(運営:Ruby 関西)出席 - Rubyとか Illustratorとか SFとか折紙とか)の okkez さんによる初級者レッスンの課題、整数をローマ数字で表現する。メソッド一つ定義するだけにスペックファイル(とレイクファイル)別に作るのもなんだなあと上記ベストプラクティスに思い当たった。
#!/usr/bin/ruby # conding: utf-8 class Integer Roman = [ %w[1000 M], %w[500 D], %w[100 C], %w[50 L], %w[10 X], %w[5 V], %w[1 I], ] def to_roman roman, = Roman.inject(['', self]) do |result, num_letter| [ result[0] + num_letter[1] * (result[1] / num_letter[0].to_i), result[1] % num_letter[0].to_i, ] end #roman, = Roman.inject(['', self]) do |result, num_letter| Roman.each_cons(3) do |major, half, minor| next unless half[0][0, 1] == '5' roman.sub! half[1] + minor[1] * 4, minor[1] + major[1] roman.sub! minor[1] * 4, minor[1] + half[1] end #Roman.each_cons(3) do |major, half, minor| roman end #def to_roman end #class Integer if $0 == __FILE__ then ARGV.each{|arg| arg.to_i.to_roman.+("\n").display } end #if $0 == __FILE__ __END__ describe Integer do it 'should be true' do; Integer.should be_true; end context '#to_roman' do subject{ rand 100 } it{ should respond_to(:to_roman) } it{ lambda{ subject.to_roman }.should_not raise_error } describe Bignum do subject{ 2**62 } it{ should be_a_instance_of(Bignum) } it{ should respond_to(:to_roman) } #it{ lambda{ subject.to_roman }.should_not raise_error } it{ lambda{ subject.to_roman }.should raise_error(NoMemoryError) } end #describe Bignum do end #context '#to_roman' do end #describe Integer do describe 1 do subject{ 1 } its('to_roman'){ should == 'I' } end #describe 1 do subject{ 1 } [ %w[1 I], %w[2 II], %w[3 III], %w[4 IV], %w[5 V], %w[6 VI], %w[7 VII], %w[8 VIII], %w[9 IX], %w[10 X], %w[11 XI], %w[12 XII], %w[13 XIII], %w[14 XIV], %w[15 XV], %w[16 XVI], %w[19 XIX], %w[20 XX], %w[40 XL], %w[50 L], %w[60 LX], %w[90 XC], %w[99 XCIX], %w[100 C], %w[200 CC], %w[400 CD], %w[500 D], %w[600 DC], %w[900 CM], %w[1000 M], %w[1999 MCMXCIX], %w[2000 MM], %w[3000 MMM], ].each do |num, roman| describe num do subject{ num.to_i } its('to_roman'){ should == roman } end #describe num do subject{ num.to_i } end # each do |num, roman|