Functional Problem Solving (CSC 151 2016S) : Labs

Laboratory: Unit Testing with RackUnit


Summary: In the laboratory, you will explore the ways in which small tests can help you develop and update code. You will also familiarize yourself with the RackUnit unit testing library.

Preliminaries

a. After starting DrRacket, add (require gigls/unsafe), (require rackunit) and (require rackunit/text-ui) to your definitions pane and click Run.

b. Read the following procedures to make sure that you understand what they do, and then add the code to your definitions pane.

;;; Procedure:
;;;   drawing-center-x
;;; Parameters:
;;;   drawing, a drawing
;;; Purpose:
;;;   Get the x coordinate of the center of a drawing
;;; Produces:
;;;   x, a real number
;;; Preconditions:
;;;   drawing has positive width and height.
;;; Postconditions:
;;;   (- (drawing-right drawing) x) is approximately (- x (drawing-left drawing))
(define drawing-center-x
  (lambda (drawing)
    (+ (drawing-left drawing) (* 1/2 (drawing-width drawing)))))

;;; Procedure:
;;;   drawing-center-y
;;; Parameters:
;;;   drawing, a drawing
;;; Purpose:
;;;   Get the y coordinate of the center of a drawing
;;; Produces:
;;;   y, a real number
;;; Preconditions:
;;;   drawing has positive width and height.
;;; Postconditions:
;;;   (- (drawing-bottom drawing) y) is approximately (- y (drawing-top drawing))
(define drawing-center-y
  (lambda (drawing)
    (+ (drawing-top drawing) (* 1/2 (drawing-height drawing)))))

c. Suppose we are going to have to work with a drawing called (center-at drawing x y), which is supposed to make a copy of drawing, centered at the point (x,y). Sketch the postconditions for that procedure.

Exercises

Exercise 1: A Simple Experiment

Traditionally, we might “test” an implementation of this procedure by rendering some drawings centered at various positions. For example, here is some code that students might have written.

> (image-show (drawing->image (center-at (drawing-scale drawing-unit-square 50)
                                         50 50) 
                              100 100))
> (image-show (drawing->image (center-at (drawing-scale drawing-unit-square 30) 
                                         70 70) 
                              100 100))

They would then “eyeball” the images to make sure that they are in the right location. The first should be centered at (50,50), the second should be centered at (70,70).

What potential flaws do you see in their testing process?

Exercise 2: Converting to Rackunit

To avoid some of the flaws you may have noted in the prior approach, many programmers express their tests using a framework like Rackunit. Doing so allows them to automatically check results without having to spend the effort of looking at them by hand.

Here is a simple test suite based on the prior experiments.

(define test-center-at
  (test-suite
   "Tests of center-at"
   (test-case 
    "Example One"
    (check-= (drawing-center-x (center-at (scale-drawing 50 drawing-unit-square)
                                          50 50))
             50
             1)
    (check-= (drawing-center-y (center-at (scale-drawing 50 drawing-unit-square)
                                          50 50))
             50
             1))
   (test-case
    "Example Two"
    (check-= (drawing-center-x (center-at (scale-drawing 30 drawing-unit-square)
                                          70 70))
             70
             1)
    (check-= (drawing-center-y (center-at (scale-drawing 30 drawing-unit-square)
                                          70 70))
             70
             1))))

Note that the “1” that is the last parameter to each call to check-= says that we will accept an answer that is within one unit of the expected point. As you've seen in your work with drawings, many computations are unavoidably off by one pixel, and so we have accommodated for that issue.

a. What do you see as advantages or disadvantages of this approach as compared to one from the previous exercise?

b. Think about (but do not add) any additional tests that you think might be useful.

Exercise 3: Testing a Procedure

Here is an implementation of center-at.

(define center-at
  (lambda (drawing x y)
    (vshift-drawing x (hshift-drawing y drawing))))

a. Does it seem to be correct? Why or why not?

b. Do you expect it to pass the test suite we designed?

c. Run the test suite to see whether or not it passes. You can use the following command.

> (run-tests test-center-at)

d. Unfortunately, it's an incorrect implementation because it shifts vertically by x and shifts horizontally by y rather than x. Hence, we should extend our test suite. Write additional tests that would identify that problem.

Exercise 4: Another Implementation

Here is a revised implementation of center-at.

(define center-at
  (lambda (drawing x y)
    (vshift-drawing y (hshift-drawing x drawing))))

a. Does it seem to be correct? Why or why not?

b. Do you expect it to pass the revised test suite?

c. Run the test suite to see whether or not it passes.

d. As you may have observed, this implementation will not work correclty for a drawing that is not originally centered on the origin. If this version passes your test suite, add additional tests that identify that problem.

Exercise 5: And Another Implementation

Here is an even more revised implementation of center-at.

(define center-at
  (lambda (drawing x y)
    (vshift-drawing 
     (abs (- y (drawing-center-y drawing)))
     (hshift-drawing
      (abs (- x (drawing-center-x drawing)))
      drawing))))

a. Does it seem to be correct? Why or why not?

b. Do you expect it to pass the revised test suite?

c. Run the test suite to see whether or not it passes.

d. As you may have observed, this implementation will not work correclty for a drawing that must be shifted left or up. If this version passes your test suite, add additional tests that identify that problem.

Exercise 6: Yet Another Implementation

Here is a revised implementation of center-at that is intended to fix the problems observed above.

(define center-at
  (lambda (drawing x y)
    (vshift-drawing 
     y
     (hshift-drawing
      x
      drawing-unit-square))))

a. Does it seem to be correct? Why or why not?

b. Do you expect it to pass the revised test suite?

c. Run the test suite to see whether or not it passes.

d. As you may have observed, this implementation makes a square, rather than using the original drawing. We can tell that it's incorrect because the result has the wrong width and height. But many test suites don't check the width and height. If this version passes your test suite, add additional tests that identify that problem.

Exercise 7: And Yet Another Implementation

The revisions continue!

(define center-at
  (lambda (drawing x y)
    (vshift-drawing 
     y
     (hshift-drawing
      x
      (hscale-drawing
       (drawing-width drawing)
       (vscale-drawing
        (drawing-height drawing)
        drawing-unit-square))))))

a. Does it seem to be correct? Why or why not?

b. Do you expect it to pass the revised test suite?

c. Run the test suite to see whether or not it passes.

d. As you may have observed, this implementation will not work correclty for a drawing that is not originally centered on the origin. If this version passes your test suite, add additional tests that identify that problem. Note that the drawing-type method returns a value that indicates the type of drawing (ellipse, rectangle, or group).

Exercise 8: Additional Tests

Add any other tests that you think will be useful.

Exercise 9: Postconditions

At the beginning of this lab, you sketched postconditions for the center-at method. You have now thought more carefully about that procedure. Write postconditions for center-at. You should refer to the shifted drawing as “result”.

For Those With Extra Time

Extra 1: A Helper Procedure

Consider the following procedure and procedure call.

(define check-recentered-drawing
  (lambda (original modified x y)
    (check-= (drawing-center-x modified) x 1)
    (check-= (drawing-center-y modified) y 1)
    (check-= (drawing-width modified) (drawing-width original) .001)
    (check-= (drawing-height modified) (drawing-height original) .001)
    (check-equal? (drawing-color modified) (drawing-color original))
    (check-equal? (drawing-type modified) (drawing-type original))))
> (define shape1
    (drawing-hshift 11 
                    (drawing-vshift 12 
                                    (drawing-scale 10 
                                                   drawing-unit-circle))))
> (test-case 
   "shape1 at (17.2, -230/11)"
   (check-recentered-drawing shape1 (center-at shape1 17.2 -230/11) 17.2 -230/11))

Why might a programmer choose to include such a procedure in zir test programs?

Extra 2: Another Helper Procedure

Here's another helper procedure.

(define crd
  (lambda (drawing x y)
    (check-recentered-drawing drawing (center-at drawing x y) x y)))

Why might a programmer include this procedure in zir code?

Extra 3: A List of Tests

Make as comprehensive a list as possible of things you would want to test.