"

Testing Goals

As you may have guessed from the previous section, verifying that your program meets the expected behavior is important. The rest of this chapter will discuss a systematic way to ensure your program is thoroughly tested. The ultimate goal of writing tests is to reach “complete code coverage“. This means that every single line of code you wrote has been tested at least once, including all possible branches!

Unit Tests

When testing a program, you want to test the smallest part possible while still being a complete piece. The key is to test as small a piece of the system as possible and to test it as thoroughly as possible. If the individual pieces work, then it’s likely that the program as a whole will work. In some cases however, certain parts of classes or of a program as a whole are not divisible into isolated, atomic units and as such must be tested as a whole.

By testing small pieces, you can dramatically narrow down where an error occurs. If testGetFoo fails, it’s fairly obvious something in the getter isn’t working correctly. Perhaps the wrong value is being returned or the value of the instance variable wasn’t set correctly. If testGetInputAndCalculate fails, the scope of the bug is much larger. It could be incorrect input, getting the input incorrectly, storing the input incorrectly, calculating incorrectly, passing the wrong values to calculate, etc.

These small, complete tests are called unit tests. A unit test is a complete test of a small, self-contained sub-system. For example, when testing a specific class, each individual method could be considered a unit. When testing a program with a menu, testing each menu choice would be a unit. Note that there is a difference between a unit test and a partial test of some aspect of the whole system. A complete unit with a menu would be testing that when a particular option is chosen, all of the consequences of choosing that option occurs (e.g. if you choose Print, you want to ensure all of the data is printed and printed correctly). Partial testing would be testing that certain input triggered specific menu options, but not verifying that the output was correct.

Example

Let’s look at an example.

Consider the following Fraction class:


/**
 * Represents a reduced fraction like 1/4
 */

public class Fraction {

    private int numerator;
    private int denominator;

    public Fraction(int top, int bottom) {
        numerator = top;
        denominator = bottom;
        reduce();
    }

    public int getNumerator() {
        return numerator;
    }

    public int getDenominator() {
        return denominator;
    }

    public Fraction multiply(Fraction other) {
        return new Fraction(this.numerator * other.numerator, this.denominator * other.denominator);
    }

    @Override
    public String toString() {
        return numerator + "/" + denominator;
    }

    private void reduce() {
        int max = Math.max(numerator, denominator);
        for (int i = max; i > 1; i--) {
            if (numerator % i == 0 && denominator % i == 0) {
                numerator /= i;
                denominator /= i;
            }
        }
    }
}

Each method in this class could be considered a unit and should be tested individually. However, there are two methods that cannot be tested without the help of other methods – the constructor and *reduce*. If the constructor test consists only of calling the constructor, you can verify that no exceptions were caused. However, this does not fully test the functionality of the constructor.

Test Yourself

  • What else do you expect the constructor to do?

Thus, to fully test the constructor, you need to determine if the instance variables were set correctly. You can do this by calling the getters or by calling toString.


Fraction half = new Fraction(1, 2);

if(half.toString().equals("1/2")){

    System.out.println("Passed!");

} else {

    System.out.println("Failed!");

}

Test Yourself

  • Why can’t you directly test reduce?
  • Which method could you call to indirectly test reduce?

 

Note that since we cannot directly call reduce, we must rely on testing the side effects of the method. In black box testing, tests focus on ensuring the public interface works. Since you cannot directly test reduce, you don’t write tests that try to test reduce,. If you have sufficiently tested the public methods, the functionality of reduce, will end up being tested anyway. In this instance, if you correctly tested the constructor and expected the object to be constructed in a completely reduced form, you were indirectly testing that reduce, worked.

definition

License

Icon for the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License

Computer Science II Copyright © by Various is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License, except where otherwise noted.