unit-testingrandomlispracketrackunit

How to use a unit-testing framework for a function that has a dynamic output in Racket?


I was doing exercise 3.5 on SICP book.

A Monte Carlo implementation in Racket generates the following code:

(define (monte-carlo trials experiment)
  (define (iter trials-remaining trials-passed)
    (cond ((= trials-remaining 0)
           (/ trials-passed trials))
          ((experiment)
           (iter (- trials-remaining 1) (+ trials-passed 1)))
          (else
           (iter (- trials-remaining 1) trials-passed))))
  (iter trials 0))

(define (random-in-range low high)
  (let ((range (- high low)))
    (+ low (random range))))
; questão propriamente dita
(define (estimate-integral predicate x1 x2 y1 y2 trials)
  (*(* (- x1 x2) (- y1 y2))
    (monte-carlo trials predicate)))

(define (circulo?)
  (>= 1 (+ (square (random-in-range -1 1))
           (square (random-in-range -2 1)))))

(define (estimate-pi)
  (estimate-integral circulo? -1.0 1 -2 1 100000))
; Fiquei brincando com os os parametros do retangulo, desde que o circulo continue dentro dele
(define square
  (lambda (x) (* x x)))

The code is right. However, I decided to insert some unit tests.

Hence, I imported the library rackunit and used the following test:

(require rackunit)

(check-equal? (estimate-pi) 3.00906)

This is obviously a problem and the tests will always fail since the output is dynamic due to a random function.

What should I do in this case?


Solution

  • There's a built-in check for this - the preferred solution:

    (check-= (estimate-pi) 3.1416 1e-4 "Incorrect value for pi")
    

    The previous check verifies that the result falls within an acceptable tolerance value (also known as epsilon), and it's equivalent to this:

    (check-true (<= (abs (- (estimate-pi) 3.1416)) 1e-4))
    

    The 1e-4 part is the tolerance in scientific notation, it means that if the difference between the actual value and the expected value is less than 0.0001, then we accept the result as correct. Of course, you can tweak the tolerance value to suit your needs - the smaller the number, the higher the required precision. Also, it's useful to know that if check-= didn't exist, we could define our own check, like this:

    (define-binary-check (check-in-tolerance actual expected tolerance)
      (<= (abs (- actual expected)) tolerance))
    
    (check-in-tolerance (estimate-pi) 3.1416 1e-4)