これでいいと自信を持ってリリースしても、バグレポートを受け取ってしまうことは、さけられません。
バグレポート:「重ならない2つの矩形を指定しても、重なり部分の面積が求められるのはおかしいのではないでしょうか。」
こういう場合は考えていなかったので、テストケースに含まれていませんでした。“重ならない2つの矩形の重なり部分の面積”というのは、どうなれば正解なのでしょうか。ユーザと検討した結果、「重ならない2つの矩形のintersectはエラーにする。」という結論になりました。
やっぱりここでもテストを書くことから始めます。ユニットテストに組み込んでおけば、「フィックスしたはずのバグが気付かないうちにまた再現していた」なんてことは避けられます。(回帰テスト)
def testNotIntersected() rectA = Rectangle.new(Point.new(0, 0), Point.new(20, 30)) rectB = Rectangle.new(Point.new(40, 40), Point.new(50, 50)) begin rectA.intersect(rectB) assert(false, "intersect does not failed") rescue RuntimeError end end
beginとrescueは、C++やJavaならtryとcatchです。発生するはずの例外を捕まえて握りつぶし、例外が発生する次の行に来てしまったらテストを失敗させるのが、例外のテストの標準形です。assert(false, xxx)は必ずfailするassertです。(Test::Unitなら例外をチェックするassert_raisesというのがあるので、そっちを使ったほうがいいでしょう。)
テストします。
![]() |
失敗を確認したところで、実装です。
def isIntersect(another) return another.getOrigin() < @corner end def intersect(another) unless (isIntersect(another)) raise "notIntersected" end return self.class().new( @origin.max(another.getOrigin()), @corner.min(another.getCorner())) end
ちょっと横着をして一度に何行も実装しました。
テストします。
![]() |
Pointに<というメソッドを作っていなかったのでエラーがたくさん出ています。Pointに<を定義しましょう。<は、引数のpointより自分が左上にあればtrueを返すメソッドのつもりですが、ひとまずtrueを返しておきます。
def <(point) return true end
テストします。
![]() |
$ ruby RectangleTest.rb Loaded suite RectangleTest Started ........F Finished in 0.08 seconds. 1) Failure: testNotIntersected(RectangleTest) [RectangleTest.rb:72]: intersect does not failed.is not true. 9 tests, 10 assertions, 1 failures, 0 errors
これでtestNotIntersectedだけがFailする状態に戻りました。Pointの<を正しい実装に直しましょう。
def <(point) return (@x < point.getX()) && (@y < point.getY()) end
テストします。
![]() |
無事Greenですが、これで終わりにしないで、Pointのテストに<のテストをちゃんと追加しておきましょう。
ここで、また疑問が出てきます。今でも直接なら、幅や高さが負になるようなRectangleは作ることが可能です。それでいいんでしょうか。テストにしてみます。
def testMinusRect() minusRect = Rectangle.new(Point.new(40, 40), Point.new(20, 30)) assert_equal(-20, minusRect.width()) assert_equal(-10, minusRect.height()) assert_equal(200, minusRect.area()) end
これが期待した動作なのかどうか、もう一度Rectangleのユーザと話し合う必要がありそうです。(普通なら、こんなことはもっと前に気付くでしょうけど。)