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.
a. After starting DrRacket, add (require gigls/unsafe),
(require rackunit) and
(require rackunit/text-ui) to your definitions pane and
click .
b. Read the following procedures to make sure that you understand what they do, and then add the code to your definitions pane.
;;; Procedure:
;;; irgb-scale
;;; Parameters:
;;; color, an integer-encoded RGB color
;;; factor, a real number
;;; Purpose:
;;; Scale each component in irgb by factor
;;; Produces:
;;; scaled, an integer-encoded RGB color
;;; Preconditions:
;;; 0 <= factor <= 1
;;; Postconditions:
;;; (irgb-red scaled) = (floor (* scale (irgb-red color)))
;;; (irgb-green scaled) = (floor (* scale (irgb-green color)))
;;; (irgb-blue scaled) = (floor (* scale (irgb-blue color)))
(define irgb-scale
(lambda (color factor)
(irgb (* factor (irgb-red color))
(* factor (irgb-green color))
(* factor (irgb-blue color)))))
;;; Procedure:
;;; irgb-half
;;; Parameters:
;;; color, an integer-encoded RGB color
;;; Purpose:
;;; Scale each component of color by half
;;; Produces:
;;; half, an integer-encoded RGB color
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; (irgb-red half) = (floor (* 1/2 (irgb-red color)))
;;; (irgb-green half) = (floor (* 1/2 (irgb-green color)))
;;; (irgb-blue half) = (floor (* 1/2 (irgb-blue color)))
(define irgb-half (section irgb-scale <> 1/2))
Suppose we are going to check the accuracy of an implementation of
( that computes the average of
two RGB colors.
my-irgb-average color1
color2)
a. Sketch the preconditions of
.
my-irgb-average
b. Sketch the postconditions of
.
my-irgb-average
Traditionally, we might “test” an implementation of this procedure by creating some different colors and showing them. For example, here is some code that students might have written.
>(define fave1 (irgb 150 0 100))>(define fave2 (irgb 0 50 0))>(define fave3 (irgb 100 100 100))>(image-show (color-swatch fave1(my-irgb-average fave1 fave2)fave2(my-irgb-average fave2 fave3)fave3))
They would then “eyeball” the images to make sure that that the colors seem “right”. The second color should be between the first and middle; the fourth color should be between the middle and last.
What potential flaws do you see in their testing process?
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. Note that we've hard-coded the colors so that we don't have to look elsewhere for their values.
;;; Name:
;;; test-my-irgb-average
;;; Type:
;;; Test suite
;;; Value:
;;; Tests of the my-irgb-average procedure
(define test-my-irgb-average
(test-suite
"Tests of my-irgb-average"
(test-case
"Example one"
(check-equal? (irgb-red (my-irgb-average (irgb 150 0 100) (irgb 0 50 0)))
75
"red")
(check-equal? (irgb-green (my-irgb-average (irgb 150 0 100) (irgb 0 50 0)))
25
"green")
(check-equal? (irgb-blue (my-irgb-average (irgb 150 0 100) (irgb 0 50 0)))
50
"blue"))
(test-case
"Example two"
(check-equal? (irgb-red (my-irgb-average (irgb 0 50 0) (irgb 100 100 100)))
50
"red")
(check-equal? (irgb-green (my-irgb-average (irgb 0 50 0) (irgb 100 100 100)))
75
"green")
(check-equal? (irgb-blue (my-irgb-average (irgb 0 50 0) (irgb 100 100 100)))
50
"blue"))))
Note that the "red", "green", or
"blue" that is the last parameter to each call to
gives a word to print if
that comparison fails.
check-equal?
a. What do you see as advantages or disadvantages of this approach as compared to one from the previous exercise?
b. List (but do not add) any additional tests that you think might be useful.
Here is an implementation of my-irgb-average,
based on the rule that “the average of a and b is half the
sum of a and b”.
(define my-irgb-average
(lambda (color1 color2)
(irgb-half (irgb-add color1 color2))))
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-my-irgb-average)
d. Although this implementation passes the test suite, the implementation
is not correct. Why not? Because when we add two components, we cap
the sum at 255. Our tests have not checked for that case. Write an
additional test case that averages (irgb 200 180 200)
and (irgb 100 180 180) and checks that the components
of the result are 150, 180, and 190.
e. Determine whether or not the revised test suite finds an error in
my-irgb-average. (It should.)
Here is a revised implementation of my-irgb-average
that avoids the overflow issue by halving each color before adding.
It follows the rule that “the average of a and b is the sum
of half a and half b”.
(define my-irgb-average
(lambda (color1 color2)
(irgb-add (irgb-half color1) (irgb-half color2))))
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. There is a subtle error in this implementation. Identify that error and insert a test to catch that error.
The test suite, as written, is a bit clumsy. First, we have nearly identical code for each of the four or so major tests. Second, we have to remember to enter the same pair of colors three times per test. Third, we need to do the computation of the average value by hand. When we are doing repeated work, it is often helpful to have a separate procedure.
;;; Procedure:
;;; test-case-my-irgb-average
;;; Parameters:
;;; name, a string
;;; color1, an integer-encoded RGB color
;;; color2, an integer-encoded RGB color
;;; Purpose:
;;; Generate a test case for my-irgb-average
;;; Produces:
;;; case, a test-case
(define test-case-my-irgb-average
(lambda (name color1 color2)
(test-case
name
(check-equal? (irgb-red (my-irgb-average color1 color2))
(floor (* 1/2 (+ (irgb-red color1) (irgb-red color2))))
"red")
(check-equal? (irgb-green (my-irgb-average color1 color2))
(floor (* 1/2 (+ (irgb-green color1) (irgb-green color2))))
"green")
(check-equal? (irgb-blue (my-irgb-average color1 color2))
(floor (* 1/2 (+ (irgb-blue color1) (irgb-blue color2))))
"blue"))))
Now, we can rewrite that first test case much more concisely.
(define test-my-irgb-average
(test-suite
"Tests of my-irgb-average"
(test-case-my-irgb-average "Example one"
(irgb 150 0 100)
(irgb 0 50 0))
...))
Isn't that easier to read and to write?
a. Add
to your definitions pane.
test-case-my-irgb-average
b. Rewrite your test suite to use
Add .
test-case-my-irgb-average
c. Rerun your tests. You should get the same error as you were getting previously.
Add any other tests that you think will be useful. In writing these tests, you should think about other possible “edge” or “corner” cases.
Write a version of
that you expect to pass the tests.
my-irgb-average
If you find that you have time left over, you may try any of these extra problems. (You need not do them in order.)
irgb-scale
Write a test suite for .
irgb-scale
You will be testing a procedure that takes two colors as input. Make a list of good pairs of colors to work with. (Ideally, you'll come up with a list of pairs of colors that will make good tests for a variety of procedures.)