Skip to main content

Laboratory: Testing 2.0

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 our testing library.

Preliminaries

a. Open a terminal window and type the following. The instruction tells Racket where to find some of the code for today’s lab.

$ raco link /home/rebelsky/Web/Courses/CSC151/triangles

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

Exercises

Exercise 1: Testing the Tester

As you may recall from the reading, RackUnit provides a variety of procedures ot help you write tests.

(check-equal? expression expected), (check-equal? expression expected optional-message) RackUnit procedure.
Evaluate expression ... expected and then compare them for equality. If they are equal, do nothing. If they are not equal, print an error message. If the optional message is included, also print that message.
(check-not-equal? expression expected), (check-not-equal? expression expected optional-message) RackUnit procedure.
Evaluate expression ... expected and then compare them. If they are not equal, do nothing. If they are equal, print an error message. If the optional message is included, also print that message.
(check-= expression expected epsilon) , (check-= expression expected epsilon optional-message) RackUnit procedure.
Evaluate expression ... expected and then compare them for numeric equality (within *epsilon). If they are equal, do nothing. If they are not equal, print an error message. If the optional message is included, also print that message.
(test-case description check-1 ... check-n) RackUnit procedure.
Create a new test case by running a series of checks.
(test-suite description check-or-test-or-suite-1 ... check-or-test-or-suite-n) RackUnit procedure.
Create a new test suite that groups together a variety of checks, tests, and other suites. Unlike tests and checks, which are executed immediately, test suites are objects that can be run separately.

In the Interactions pane, try each of the operations a few times to make sure you understand its operation. (Yes, this instruction is intentionally vague.)

Exercise 2: Testing Triangulation

Consider the following procedure documentation:

;;; Procedure:
;;;   classify-triangle
;;; Parameters:
;;;   side1, a rational number [unverified]
;;;   side2, a rational number [unverified]
;;;   side3, a rational number [unverified]
;;; Purpose:
;;;   Determine the kind of triangle the three sides describe.
;;; Produces:
;;;   classification, a string 
;;; Preconditions:
;;;   side1, side2, and side3 together describe a triangle [verified]
;;; Postconditions:
;;;   If all three sides are equal, classification is "equilateral".
;;;   If exactly two sides are equal, classification is "isosceles".
;;;   If no two sides are equal, classification is "scalene".

a. Write a series of tests for this procedure. Your tests should look something like the following:

(require triangles/tri-0000)
(define triangle-tests
  (test-suite 
   "Testing classify-triangle"
   (test-case "unit side length equilateral triangle"
              (check-equal? (classify-triangle 1 1 1) "equilateral"))))
(run-tests triangle-tests)

b. There are approximately 40 different versions of the procedure of varying degrees of correctness. (Professor Rebelsky wrote a program to create them!) They are named as follows (and you can access them the same way as you accessed tri-0000, provided you use the raco link command in the preliminaries):

  • triangles/tri-0000
  • triangles/tri-0001
  • triangles/tri-0010
  • triangles/tri-0011
  • triangles/tri-0100
  • triangles/tri-0101
  • triangles/tri-0110
  • triangles/tri-0111
  • triangles/tri-0200
  • triangles/tri-0201
  • triangles/tri-0210
  • triangles/tri-0211
  • triangles/tri-1000
  • triangles/tri-1001
  • triangles/tri-1010
  • triangles/tri-1011
  • triangles/tri-1100
  • triangles/tri-1101
  • triangles/tri-1110
  • triangles/tri-1111
  • triangles/tri-1200
  • triangles/tri-1201
  • triangles/tri-1210
  • triangles/tri-1211
  • triangles/tri-2000
  • triangles/tri-2001
  • triangles/tri-2010
  • triangles/tri-2011
  • triangles/tri-2100
  • triangles/tri-2101
  • triangles/tri-2110
  • triangles/tri-2111
  • triangles/tri-2200
  • triangles/tri-2201
  • triangles/tri-2210
  • triangles/tri-2211

Pick eight or so of those files and run your tests on them. Which ones pass your tests? (Try to collaborate with your classmates so that all of them get tested.)

Exercise 3: Writing a Triangle Classifier

a. To the best of your ability, write the classify-triangle procedure described in the previous problem.

b. Rerun your tests using this version of the procedure. How many does it pass, how many does it fail? Do not rewrite the procedure to make it pass all these tests.

Exercise 4: A Simple Test Suite

Here is a test that someone might write for the procedure described above.

(define triangle-tests
  (test-suite "Tests of classify-triangle"
    (test-case "unit side length equilateral triangle"
               (check-equal? (classify-triangle 1 1 1) "equilateral"))
    (test-case "simple isosceles triangle"
               (check-equal? (classify-triangle 2 2 3) "isosceles"))
    (test-case "simple scalene triangle"
               (check-equal? (classify-triangle 3 4 5) "scalene"))))
(run-tests triangle-tests)

a. Does your classify-triangle procedure pass these tests? If not, repair it to ensure that it passes all the tests.

b. Run this test suite on five of the variants mentioned in problem 2 that you have either not tested or that have passed all tests so far. How many pass the new test suite?

c. Can you write an obvious solution to classify-triangle that passes all of these tests, but fails to meet the specifications?

Exercise 5: Testing Error Checking

Someone thinking carefully about the definition of classify-triangle might worry that the tests, as written, do not check for non-triangles. For example, something with side lengths 1, 1, and 3 is not a triangle.

So, what should our procedure do when given invalid inputs? It should report an error.

> (classify-triangle 1 1 3)  
classify-triangle: sides do not describe a triangle

How do we test to see if an error occurs? With the cryptic and complicated check-exn. Our test should look like the following:

(check-exn (lambda (result) #t) (lambda () (classify-triangle 1 1 3)))

This says (approximately)

Run (classify-triangle 1 1 3). If it reports an error, do nothing. If it succeeds, report that it failed to produce the expected error.

a. Add a test to the testing code above to ensure that classify-triangle rejects that non-triangle.

b. Does your version of classify-triangle pass the revised test suite? If not, correct it so that it does. Note that you can report an error with the error procedure.

c. Run this test suite on five of the variants mentioned in Exercise 2 that you have either not tested or that have passed all tests so far. How many pass the new test suite?

Exercise 6: Testing Error Checking, Revisited

Another set of inputs for which classify-triangle is supposed to fail is one in which any of the side lengths are negative.

a. Add tests to the test suite for classify-triangle to ensure that classify-triangle rejects any set of three numbers that include a zero or negative number.

b. Does your version of classify-triangle pass the revised test suite? If not, correct it so that it does.

c. Run this test suite on five of the variants mentioned in Exercise 2 that you have either not tested or that have passed all tests so far. How many pass the new test suite?

Exercise 7: Symmetric Tests

We’ve tested for each of the three kinds of triangles, but we’ve only one test for each. What if the programmer mistakenly forgets to deal with the different orderings of parameters? We should make sure that the implementation works for each.

a. Add tests to the test suite for classify-triangle to ensure that classify-triangle correctly identifies the three kinds of triangles, not matter what the ordering. In particular, make sure that you test for the three variants of isosceles triangles.

(test-case "a few isosceles triangles"
           (check-equal? (classify-triangle 2 2 3) "isosceles")
           (check-equal? (classify-triangle 2 3 2) "isosceles")
           (check-equal? (classify-triangle 3 2 2) "isosceles"))

b. Are there other tests in which the ordering might matter? (Hint: Think about some of the tests for errors.)

c. Does your version of classify-triangle pass the revised test suite? If not, correct it so that it does.

d. Run this test suite on five of the variants mentioned in problem 2 that you have either not tested or that have passed all tests so far. How many pass the new test suite?

Exercise 8: Testing Large Sides

We now might consider the range of side values for which classify-triangle should be tested.

a. Add tests that ensure that classify-triangle works for very large numbers (say, numbers greater than 10 billion).

b. Does your version of classify-triangle pass the revised test suite? If not, correct it so that it does.

c. Run this test suite on five of the variants mentioned in problem 2 that you have either not tested or that have passed all tests so far. How many pass the new test suite?

Exercise 9: Other Tests

a. Add any other tests you conceive of.

b. Be prepared to discuss those tests in class.

c. Does your version of classify-triangle pass the revised test suite? If not, correct it so that it does.

d. Run these tests on any variants mentioned in problem 2 that you have not yet tested or that have passed all the tests up through exercise 9. How many pass the tests?