Discussion 2: Unit Test Coverage

The goal of today’s discussion is to practice reading documentation and using specifications to write unit test suites that provide good coverage. A good suite of unit tests for a class should have all of its tests pass when the class definition meets its specification. In addition, (and perhaps more importantly for the unit tests to serve their role), at least one unit test within the suite should fail if the class definition deviates from its specification (i.e., the class is buggy). Can you write tests to track down these bugs?

Learning Outcomes

  1. Write comprehensive unit tests for a method given only its specification and signature.
  2. Evaluate the coverage of a unit test suite on a method or a class.

Reminder: Discussion Guidelines

The work that you complete in discussion serves as a formative assessment tool; it offers the opportunity to assess your understanding of the material and for our course staff to get a “pulse” on how things are going, so we can make adjustments in future classes. It’s also a great place to practice technical communication and collaborative problem solving. You should complete this activity in a group comprising 2-3 students. At the end of class, your discussion TA will check off your progress on the activity and report your score for the day out of 4 points, with 2 points allocated to your presence and engagement during class and 2 points allocated for your completion of that day’s activity. More information about the grading and expectations can be found in the syllabus.

This discussion is meant to help get you comfortable with writing JUnit tests and assessing their coverage, which will be an important aspect of all of our course assignments. You should complete it using your computer. We advise you to be pragmatic about your use of any web or AI resources during this activity, as this may hide foundational or logistical issues that our course staff can quickly address to put you on a good footing for the rest of the semester. Discussion activities are intended to serve as “strength training” for programming tasks we will expect on assignments and exams (and that you will encounter in future courses and careers), and their main benefit comes from thinking critically to “puzzle” them out.

Array Range Copying

This discussion will center on the copyRange() method detailed below.

This method has a similar behavior to the System.arraycopy() method (that we will use throughout the semester). However, we’ll note that our copyRange() method handles invalid inputs differently. The copyRange() method tries to copy the data from one array range to another and reports whether this copy was able to be completed successfully. It has five parameters:

  1. src: The array containing the range to be copied
  2. srcStart: The index of the first element of the range to copy
  3. dst: The array to which the range will be copied
  4. dstStart: The index of the first location where the copied data will be written
  5. length: The number of elements to be copied

For example, if a and b are variables that reference the following two int[] arrays,

then calling copyRange(a, 5, b, 2, 3) would modify the contents of b to be,

It is also possible that src and dst are alias references to the same array. Using the same array a from the above example, calling copyRange(a, 1, a, 5, 3) would modify the contents of a to be,

Your copyRange() method should be robust to invalid inputs from its client, which include

When invalid inputs are detected, the copyRange() method should return false, and no modifications should be made to either the src or dst arrays. When the inputs are valid, the range copy operation should be performed, and copyRange() should return true. No exceptions or AssertionErrors should ever propagate out of copyRange(). As is our custom in this course, you may assume that src and dst are not null.


Exercise 1: Testing copyRange()
Carefully read the above description for the copyRange() method, as well as the specifications in the src/cs2110/ArrayUtilities class. Use these specifications to develop a suite of unit tests in the tests/cs2110/CopyRangeTest file. Each test method should have an appropriate name and should be annotated with a descriptive @DisplayName written in the "WHEN <set up> THEN <expected result>" format. To achieve good coverage in your test suite, you should take care to address different scenarios that could be encountered based on different values for the copyRange() parameters. You should also be sure that your test suite includes assertions that verify all of the method post-conditions listed in the specifications. Having comprehensive unit tests will give more confidence to the method developers that their implementation is correct.

When you run unit tests against a correct implementation (i.e., one that conforms to the method specifications), they should all pass. While this offers a good indication of the soundness of your tests, it does not offer a sense of how good your test coverage is. To check this, we can run the tests against a collection of buggy implementations. Each buggy implementation deviates from the specifications in one small way. For example, it may return the wrong value for a particular edge case, misinterpret one of the parameters, ignore or introduce a side effect, etc. If your unit tests offer full coverage, then at least one test will include an assertion that enforces that part of the specifications, meaning it will fail against the buggy implementation. This is the idea behind the Gradescope autograders for your assignments. After we confirm that your tests are sound (i.e., they all pass against a correct implementation of the specifications), the autograder will run your tests against various buggy implementations and make sure at least one test fails.

Exercise 2: Check your Understanding
Why do we need to make sure that the tests are sound before using buggy implementations to assess their coverage?
Exercise 3: Checking your copyRange() Coverage
To get some practice interpreting the Gradescope output from these coverage tests, upload your CopyRangeTest.java file to the Discussion 2 assignment in Gradescope (note that you aren't being graded on the autograder output; the usual discussion grading will be used), and take a look at the autograder results. Call the TAs over if you have any trouble navigating through the Gradescope interface. The autograder includes 10 entries. The first entry checks that your unit tests compile. The second entry assesses their soundness against a correct copyRange() implementation. The remaining entries run your tests against eight buggy copyRange() implementations to assess their coverage. For the eight coverage tests:
  • If you see green output from the autograder, then your tests found the bug(s) in one of our implementations. Look through the output to diagnose the bug and describe it below.
  • If you see red output from the autograder, then the buggy code evaded your tests. Try adding additional unit tests to your suite to increase your coverage.
Buggy Implementation 1:
Buggy Implementation 2:
Buggy Implementation 3:
Buggy Implementation 4:
Buggy Implementation 5:
Buggy Implementation 6:
Buggy Implementation 7:
Buggy Implementation 8:
Exercise 4: Implementing copyRange() - Time Permitting
If you were able to track down all of the bugs, you should now have a good unit test suite that you can use for test-driven development of the copyRange() method. Try running the unit tests that you have written. As a reminder, follow these instructions from our IntelliJ guide to run tests. You should find that all of the tests fail. This makes sense; the copyRange() method hasn't been written yet.

Complete the definition of the copyRange() method. Use the unit tests that you wrote to guide your development. Choose a missing behavior or mishandled scenario that was identified by a failing unit test, and update the method definition to address this. Now, more unit tests should pass. Repeat this process until your code passes all of your unit tests.