Racket test for Exception
Asked Answered
F

1

6

I am taking my first steps with exception handling in Racket and would like to write a unit test, which checks, if a procedure raises an exception for a specific input.

The following is what I already have:

The procedure, which shall raise the exception:

(define (div-interval x y)
  ;; EXERCISE 2.10
  ; exception handling
  (with-handlers
    ([exn:fail:contract:divide-by-zero?
      (lambda (exn)
        (displayln (exn-message exn))
        #f)])

    (if (or
      (>= (upper-bound y) 0)
      (<= (lower-bound y) 0))

      (raise
        (make-exn:fail:contract:divide-by-zero
        "possible division by zero, interval spans zero"
        (current-continuation-marks)))

      (mul-interval
        x
        (make-interval
          (/ 1.0 (upper-bound y))
          (/ 1.0 (lower-bound y)))))))

The unit test:

(require rackunit)

(define exercise-test
  (test-suite
    "exercise test suite"

    #:before (lambda () (begin (display "before")(newline)))
    #:after (lambda () (begin (display "after")(newline)))

    (test-case
      "checking interval including zero"
      (check-exn
        exn:fail:contract:divide-by-zero?
        (div-interval
          (make-interval 1.0 2.0)
          (make-interval -3.0 2.0))
        "Exception: interval spans zero, possible division by zero")))

(run-test exercise-test)

There are some more tests in this test suite, but they are for other procedures, so I did not include them in this code. When I run my program, I get the following output:

before
possible division by zero, interval spans zero
after
'(#<test-success> #<test-success> #<test-success> #<test-success> #<test-error>)

Where the <test-error> is for the test case in this post.

It seems the procedure does not raise the exception.

Is this, because I have a handler in my procedure, which returns #f and thus already "ate" the exception?

How would I usually write a unit test for raised exceptions?

Footwork answered 30/9, 2016 at 13:16 Comment(0)
E
7

You are absolutely right that you the code did not raise the exception because the with-handler caught it and returned #f. If you want the with-handler to re-raise the exception for you you need to use raise, making your code look something like:

(define (div-interval x y)
  (with-handlers
    ([exn:fail:contract:divide-by-zeor?
      (lambda (e)
        <do-stuff>
        (raise e))])
    <function-body>)

Now your handler will still run, but at the end it will re-raise the exception for you.

As for testing it, you are absolutely right that check-exn is the right way to go about it. Except because check-exn is a procedure, you need to wrap your code in a thunk making it look like:

(require rackunit)
(check-exn
  exn:fail:contract:divide-by-zero?
  (lambda ()
    (div-interval
      (make-interval 1.0 2.0)
      (make-interval -3.0 2.0))))
Erythromycin answered 30/9, 2016 at 14:5 Comment(4)
Do you mean I have to wrap the division function call inside a lambda expression, in order to not evaluate the division immediately, but make it a function for it to be called inside the check-exn?Footwork
Yes, that is exactly what I mean.Erythromycin
Works fine, thanks! I noticed that on command line I still get the context of the exception printed. Is that the normal case when using check-exn or is it because I catch the exceptions once in between and re-raise it?Footwork
Its because you have (displayln (exn-message exn)) in your exception handler. If you take it out the exception won't print out any more. However, I should note that at that point you are just catching and then immediately re-raising the exception, and thus you don't really need a with-handlers at all any more.Erythromycin

© 2022 - 2024 — McMap. All rights reserved.