3.バグ発見−例外のテスト−

これでいいと自信を持ってリリースしても、バグレポートを受け取ってしまうことは、さけられません。

バグレポート:「重ならない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というのがあるので、そっちを使ったほうがいいでしょう。)

テストします。

Red bar

失敗を確認したところで、実装です。

    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

ちょっと横着をして一度に何行も実装しました。

テストします。

Red bar

Pointに<というメソッドを作っていなかったのでエラーがたくさん出ています。Pointに<を定義しましょう。<は、引数のpointより自分が左上にあればtrueを返すメソッドのつもりですが、ひとまずtrueを返しておきます。

    def <(point)
        return true
    end

テストします。

Red bar
$ 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 bar

無事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のユーザと話し合う必要がありそうです。(普通なら、こんなことはもっと前に気付くでしょうけど。)


[上] [前] [次]