2008-05-13
目次
りんご味Ruby 第24回 藤本 尚邦
■
前回は、実装すべきことを思い出すための手段として、組み込みメソッドのraiseを使って例外を発生させました。自分用のちょっとしたツールを作るくらいならこれで十分かもしれないのですが、プログラムをライブラリ的に使い回すつもりがある場合には、実装すべき機能が実装されていて、それが期待通りに動いているかどうかをチェックするためのテストを書いておくとより良いでしょう。
YahooHonyakuで実装すべき機能は、クラスメソッドのYahooHonyaku.translateおよびインスタンスメソッドのYahooHonyaku#translate です。どちらも、テキストを入力して得られた結果が、Yahoo翻訳で実行したときの結果と一致していれば、期待通りに動いているといえます。
■ Test::Unitを使う
Rubyで使うことのできるテスト用のライブラリはいくつかあるのですが、ここではRubyに標準添付されている単体テスト用ライブラリTest::Unitを使ってテストを書いてみます。
--------------------------------------------- test-yahoo-honyaku.rb
require 'test/unit'
require 'yahoo-honyaku'
class TestYahooHonyaku < Test::Unit::TestCase
# インスタンスメソッド YahooHonyaku#translate のテスト
def test_translate
translator = YahooHonyaku.new
assert_equal("Good morning",
translator.translate(:je, "おはよう"),
"和英翻訳の動作確認")
assert_equal("おはよう",
translator.translate(:ej, "Good morning"),
"英和翻訳の動作確認")
end
# クラスメソッド YahooHonyaku.translate のテスト
def test_s_translate
assert_equal("Good morning",
YahooHonyaku.translate(:je, "おはよう"),
"和英翻訳の動作確認")
assert_equal("おはよう",
YahooHonyaku.translate(:ej, "Good morning"),
"英和翻訳の動作確認")
end
end
---------------------------------------------
Test::Unit では、Test::Unit::TestCaseの派生クラスを作り、その中にテストを記述していきます。テストは「test_」で始まる名前のインスタンスメソッドとして定義します。テストの中に、「assert」で始まる名前の各種assertメソッドを使って、テストの詳細を記述します。上のテストで使っているassert_equal の場合、引数は:
第1引数: 期待される結果
第2引数: 実行結果
第3引数: メッセージ文字列(省力可能)
となります。ではテストを実行してみましょう。上のテストプログラムをtest-yahoo-honyaku.rb という名前で yahoo-honyaku.rb と同じディレクトリにファイルに保存して以下のように実行してください。
$ ruby test-yahoo-honyaku.rb
(中略)
1) Error:
test_s_translate(TestYahooHonyaku):
NotImplementedError: まだ実装してねぇ、誰か書いてくれ!
./yahoo-honyaku.rb:14:in `_eid_'
./yahoo-honyaku.rb:7:in `translate'
./yahoo-honyaku.rb:3:in `translate'
test-yahoo-honyaku.rb:17:in `test_s_translate'
2) Error:
test_translate(TestYahooHonyaku):
NotImplementedError: まだ実装してねぇ、誰か書いてくれ!
./yahoo-honyaku.rb:14:in `_eid_'
./yahoo-honyaku.rb:7:in `translate'
test-yahoo-honyaku.rb:8:in `test_translate'
2 tests, 0 assertions, 0 failures, 2 errors
このような結果になりました。テストの実行中にエラー(例外)が発生した場合には、errorとしてカウントされます。前回までに書いたtranslateメソッドは、呼び出すとエラーが発生する(raiseを使って自ら発生させている)ので、2つのテスト(test_translateおよびtest_s_translate)のそれぞれがerrorとしてカウントされたのです。
ここで、Test::Unitの動作を示すために、YahooHonyaku#translate をちょっと変更してみましょう。例外を発生させるかわりに、実装はされたもののバグがあるという意味で、間違った文字列を返すようにします。
def translate(mode, text)
"「#{text}」が翻訳できませんでした!"
end
この変更をくわえた上で test-yahoo-honyaku.rb を実行すると以下のようにな
ります:
$ ruby -Ku test-yahoo-honyaku.rb
(中略)
1) Failure:
test_s_translate(TestYahooHonyaku) [test-yahoo-honyaku.rb:18]:
和英翻訳の動作確認.
<"Good morning"> expected but was
<"「おはよう」が翻訳できませんでした!">.
2) Failure:
test_translate(TestYahooHonyaku) [test-yahoo-honyaku.rb:8]:
和英翻訳の動作確認.
<"Good morning"> expected but was
<"「おはよう」が翻訳できませんでした!">.
2 tests, 2 assertions, 2 failures, 0 errors
今度は、2つのerrorではなくて、2つのfailureがカウントされました。それぞれのテストでは、(assert_equalによって)期待と違う結果だったことがわかるメッセージが出力されているので、何が問題なのか・何を直すべきなのかが見つけやすくなります。
(注) YahooHonyaku#translate は元に戻しておいてください。
今回は、assert_equalしか紹介できませんでしたが、他にもいろいろなassertメソッドがあります。例えば、assert_raiseというメソッドは:
assert_raise(NotImplementedError) { ... }
のようにブロックを渡すことができます。ブロック実行中に指定されたエラー(この場合NotImplementedError)が発生すればOKという意味になります。assertメソッドなどを含めて、Test::Unitの詳細については、Rubyリファレンスマニュアル(日本語)をご覧ください。
■ [おまけ] RSpec
Test::Unitは、RubyプログラミングにおいてDSL的手法がまだあまり浸透していないころに作られたライブラリで、大ざっぱに言うとJavaのJUnitをそのままRubyに持ち込んだという感じでした。その後、RubyプログラミングのDSL手法を効果的に使ったRSpecというテスト用ライブラリが登場しています。
--------------------------------------------- Rspecによる仕様の記述例
# bowling_spec.rb
require 'bowling'
describe Bowling do
before(:each) do
@bowling = Bowling.new
end
it "should score 0 for gutter game" do
20.times { @bowling.hit(0) }
@bowling.score.should == 0
end
end
---------------------------------------------
残念ながら私はまだ使ったことがないのですが、Rubyの特徴を活かしたおもしろそうなライブラリです。
■ 参考URL
Rubyリファレンスマニュアル(日本語)のTest::Unit
http://www.ruby-lang.org/ja/man/html/Test_Unit.html
RSpec
http://rspec.info/
藤本裕之のプログラミング夜話 #137
ずいぶん間が空いてしまったが、元気でしたかとかGWはいかがお過ごしでしたかとかじつはワタシは数年ぶりに松本へ遊びに行ったんですよとかそういう一方的おあいそは省いて話を続ける。……どこまで行ったっけ? そうそう。「コンピュータ・ソフトウエア」とそれ以外の「ソフトウエア」すなわち文学とか音楽とかとの決定的な違いは「ハードウエアから独立していない」ってことだ、ということころまでだった。
ワタシがこの仕事を始めた1980年代の始め頃、業界ではよく「ユーザはソフトウエアをハードウエアのおまけだと思っている」てなことが、ある種のフンガイを伴って言われていた。いや、他人のせいにしてはいけませんね、ワタシ自身、訳も分からずヒトの受け売りでそういうことを言ってたような気がする。
当時はまだパソコンというものが一般的ではなく(オレだって持ってなかった)、ここで言うハードウエアというのはもっと大げさなミニコンクラスの(若いヒトは「?」と思うかも知れぬが、ミニコンというパソコンより大っきなコンピュータがあったのだ)機械のことで、それを買うのはつまりそれで動作する業務用ソフトウエアをコミで買うことであったから、別にユーザの認識が間違ってたわけではない。
それどころか営業に言わせれば、ユーザの多く(今でもそういうヒトはいるんだけど)にとって、自分が金を払うのはこの、でっかくて中に部品がぎっしり詰まり、うんうん唸りながら動くので特別に冷房をつけっぱなしにしておく「マシンルーム」なるものまで要求する贅沢な機械なんであって、営業に連れられてきた頭ぼさぼさでネクタイもちゃんと締められないような若いの(というのは別に若き日のオレのことではありません)が機械に差し込んでカチャカチャ言わせてる封筒入りのソノシートみたいなものに記録された「ナニカ」ではなかった。
早い話、仕入れ値300万のハードウエアに、400万かけて業務アプリを載せ、利益100万乗っけて800万で売る場合(乱暴な話だがタトエだからね)、ユーザへの見積書にハード350万、ソフト450万と書いちゃうとペケで、ハード700万、ソフト100万なら納得してもらえるみたいな状況だったんですわ。こないだ観た映画「ゲゲゲの鬼太郎」の中で井上真央ちゃんがウェンツ瑛士演じる鬼太郎に「私、目で見て触れるものしか信じないの」と言うシーンがあったけど、あんな感じかしら。
で、その井上真央ちゃん……つうか役の上では実花ちゃんとか言うんだが、にちょっとホの字である鬼太郎は自分だけでなく自分の属する「妖怪」というものの存在を彼女に認めてもらおうと奮闘する。当時の我々も、自分たちが作るソフトウエアというものを、ハードウエアから独立した一個の商品たらしめんと、粉骨砕身……とまではいかないかも知れないがそれなりに努力もし、また時代の趨勢がそっちに向かっていくのは水が高いほうから低いほうへ流れるがごとき歴史の必然である、と思ってしまったんである。今思えば。
しかるに、だ。前回まで観てきたように、コンピュータ・ソフトウエアというものは最終的にハードウエアから自由になれない。経済的な問題に立ち戻った表現をすれば、ハードウエアの更新に順応しなければソフトウエアは商品価値を失ってしまうのである。つまりね、悔しいけどある意味においてソフトウエアはやっぱりハードウエアの「おまけ」なんであってそこにはけして乗り越えられない壁があったのだ。しかもそれはそこにずっとあったのにオレたちはそれをまるで妖怪のごとく「見たくないから見てこなかった」んぢゃないかと。
かつて、マルクス=エンゲルスを読んで社会主義から共産主義への移項こそが歴史の必然だと信じた人々が味わったのと同じ蹉跌を、ここに来て我々も目の当たりにしているわけだ……と言ったらすげぇ大げさに聞こえるだろうけれども、この……夢も希望もないというか身も蓋もないというか事実の上にしっかりと視座を据えないと、ソフトウエア産業の今後を展望することは難しい、とまず思うのである。
(以下次回 2008_05_10)
高橋真人の「プログラミング指南」第135回
〜XcodeによるPowerPlant X入門(24)〜
こんにちは、高橋真人です。
連休の関係で少し間が開いてしまいましたが、早速続きを始めます。
さて、前回の最後で投げかけておいた質問の答えですが、お分かりになったでしょうか? 正解をお教えする前に、プログラムにさらにちょっとだけ手を加えてみます。MyView.cpのDoControlDraw()関数の、今回のプログラムで加えた
::CGContextStrokeRect(inContext, frame);
のあとに以下を加えてください。
::CGContextMoveToPoint(inContext, frame.origin.x, frame.origin.y);
::CGContextAddLineToPoint(inContext,
frame.origin.x + frame.size.width,
frame.origin.y + frame.size.height);
::CGContextStrokePath(inContext);
詳細の説明は省きますが、要はCGContextStrokeRect()で描いた矩形枠に単に斜線を追加しているだけです。
それでは早速動かしてみてください。加えた斜線は見えましたか?
「うーん、よく見えない。よぉーく見てみると、ビューの隅にチラっと...」。もしくはLeopard上で動かしている方は、ボタンが真四角なために加えた斜線が全く見えないかもしれません。*注
それでは、開いたウインドウをディアクティベート、つまりアクティブでない状態にしてみてください。アプリケーションを別のものに切り替えてしまっても構わないのですが、そうすると他のアプリのウインドウに覆われてしまって肝心のビューがよく見えない場合もあるかもしれませんので、ウインドウをさらにもう一つ新規に作ってみるのがいいでしょう。
表示されている2つのウインドウのうち、アクティブでない方にあるボタンの上に斜線が引かれているのが今度はご覧になれると思います。これが、今回のプログラムの狙いです。
斜めの線を引いたのは、あくまでも前回作った矩形枠がボタン(コントロール)に対して前後関係がどうなっているのかを分かりやすくするためです。それを踏まえて改めて観察してみると、矩形枠自体もビューのアクティブ状態によってボタンのコントロールとの前後関係が入れ替わることが分かると思います。
ちなみに、ここで問われている“アクティブ状態”は、ウインドウのものではなくて、このビュー(MyView)側の問題です。その証拠に、ウインドウがアクティブなままで、このボタンだけをディアクティベートしても状況は同じになります。
試してみたい方は、どこか適切な場所でMyViewに対してSetEnabled(PPx::enabled_No)を呼んでやれば確認できます。
(PPx::enabled_Noは、falseの別名)
入れる場所は、MyApplication.cpのDoSpecificCommand()の中で、ビューを生成した後であればどこでも構わないでしょう。試しに、ウインドウを表示する直前に入れてみると以下のような感じになります。
view->SetEnabled(PPx::enabled_No); // ここが追加したコード
::RepositionWindow(...); // 引数は省略
theWindow->Show();
さて、前回の最後に提示した問題点である“ちょっと困ったこと”とは、このことを言っていたのです。つまり、PPx::PushButtonにサブクラスで視覚的な装飾を加えてみたくても、ビューのアクティブ状態によって見え方が変わってしまう(前後関係をコントロールできない)ということです。
それでは、何でビューのアクティブ状態によって、枠とコントロールの前後関係が入れ替わってしまうのでしょうか?
MyViewの実装コードを観察していただけば分かりますが、このビューの描画処理部分(DoControlDraw())には、コントロールの描画処理は記述されていません。それなら、コントロールはどこで描かれているのでしょう?
ところで、MyViewの継承元であるPushButtonには、DoControlDraw()そのものがないことは、前回も触れた通りです。だとしたら、一体どうやってボタンのコントロール(のイメージ)が見えているのでしょうか?
この辺は、PPxにおけるViewの実装の仕組みに大きく関わってくることですので、回を改めて、次回に少し突っ込んで調べてみたいと思います。
注:前回、「実行環境によってボタンの見た目が違う場合がある」と申しました。実は、Leopardの特定の環境ではボタンが通常の角丸のものではなく、真四角の、いわゆるベベルボタンになってしまう現象が起きていたのです。ところが、今回試したところまたボタンは従来の角丸に戻っていました。少し調べてみたところ、ボタンの高さを22ピクセル以内に納めていれば角丸のボタンになり、それを23ピクセル以上だとベベルボタンになります。ただ、以前試した時にはこのようなことにはなりませんでした。Mac OS Xのバージョンのみならず、Xcodeなどとの組み合わせに関しても細かく実験してみないと、真相は見えてこないかもしれません。
◇MOSAからのお知らせと編集後記は割愛します◇