1. Reading

  • Horstmann, Ed 11, chapter 8, Generic Programming section 8.1-8.8

2. Code Coverage and Code Complexity

A popular and readily available method to get some estimate of the value of your tests is how much of the code it 'covers', that is executes.

If all is well (TDD), it is easy to reach 100% code lines covered, including every branch or loop. Sometimes those nasty checked exceptions are in the way, because you either have to catch them and have a hard time reaching that catch block, because it is hard to setup a test case where the required exception actually occurs, thereby excluding the catch block from coverage.

In sebipom we provide Jacoco as the coverage plugin.

Measuring coverage involves a technique that is called instrumenting the (business) code. To do that effectively, the instrumenting agent inserts something like a flagging statement at the beginning of each method and of each branch or loop. Jacoco calls it a probe.

To show this, compare the bytecode ( using javap -v Fraction.class) of the method Fraction plus(Fraction) and its instrumented version.

Java source code snippet
    public Fraction plus( Fraction other ) {
        return plus( other.num, other.denom );
    }
Orginal byte code
public fraction.Fraction plus(fraction.Fraction);
  descriptor: (Lfraction/Fraction;)Lfraction/Fraction;
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=3, locals=2, args_size=2
       0: aload_0
       1: aload_1
       2: getfield      #6                  // Field num:I
       5: aload_1
       6: getfield      #7                  // Field denom:I
       9: invokevirtual #9                  // Method plus:(II)Lfraction/Fraction;
      12: areturn
    LineNumberTable:
      line 80: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      13     0  this   Lfraction/Fraction;
          0      13     1 other   Lfraction/Fraction;
Byte code instrumented by jacoco
public fraction.Fraction plus(fraction.Fraction);
  descriptor: (Lfraction/Fraction;)Lfraction/Fraction;
  flags: (0x0001) ACC_PUBLIC
  Code:
    stack=6, locals=3, args_size=2
       0: ldc           #133     // PROBE Dynamic #2:$jacocoData:Ljava/lang/Object;
       2: checkcast     #135     // PROBE class "[Z", array of boolean
       5: astore_2               // PROBE save array ref in local
       6: aload_0
       7: aload_1
       8: getfield      #6                  // Field num:I
      11: aload_1
      12: getfield      #7                  // Field denom:I
      15: invokevirtual #9                  // Method plus:(II)Lfraction/Fraction;
      18: aload_2                // PROBE Load array reg from local
      19: bipush        8        // PROBE push array index which happens to be the nineth value in the array for this block
      21: iconst_1               // PROBE push constant 1
      22: bastore                // PROBE save in array using value and index
      23: areturn                // original return
    LineNumberTable:
      line 80: 6
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          6      18     0  this   Lfraction/Fraction;
          6      18     1 other   Lfraction/Fraction;

You can imagine that instrumenting will slow your code down, however, it makes no functional changes. If your read the above code and comments we added, you see that whenever this method is executed, the 9th Boolean in the jacoco coverage array is set to one.

Having only a Boolean per code-path is well a considered compromise. If you would like to count the number of executions, the probe-code would get longer and the execution substantially slower (updating counts, etc). Jacoco chose this solution, cobertura another once popular coverage tool the other. You see that this efficient code probing can have a serious impact on the number of instructions for short (e.g. one statement) methods.

We showed a simple example of a straight through method. When a method has loops or branches each of those will have a probe of its own. From this you can infer the the coverage agent needs to know about these loops and branches and uses them for another purpose too.

2.1. Code Complexity

Control flow graph of function with loop and an if statement without loop back
Figure 1. from wikipedia

Complex code is never a good thing. It is hard to write, hard to read and therefor hard to maintain, and all in all expensive. This simplest code is just a list of assignments and method calls, properly mixed to make it’s execution meaningful. Such code would count as one code block and only needs one probe.

Introducing one or more branches increases complexity, and because paths after these branches can exclude one another, thereby increasing that chance that one or the other code-path is not executed.

This complexity can be computed from the number of code-paths found. This complexity computation also know as McCabe Cyclomatic Complexity.

When using jacoco, you get this complexity computation for free, because it is useful in the context of code coverage AND jacoco already has all the data.

high complex
Figure 2. High complexity leads to low coverage. The highlighted column is the most interesting.

In the jacoco report above you see the complexity ranging from 1 to 19. As a rule of thumb, something above 5 is reason for concern, with the exception of the boolean equals(Object) method, that starts with three and grows by one with every field considered in the equals method. According to the doc, there are ways to exclude equals from the jacoco report, but that will be for another time.

low complexity
Figure 3. Low complexity means easier in everything including in reaching high coverage.

The second jacoco report is the same application implemented by a different developer. Here the complexity ranges from 1 to 5 which leads to way easier to maintain code.

If you got curious about java byte code, the byte code listing above were generated as follows

mvn clean compile
javap -v target/classes/fraction/Fraction.class > Fraction.class-orig.txt
mvn jacoco:instrument
javap -v target/classes/fraction/Fraction.class > Fraction.class-instrumented.txt

and then editing out the relevant pieces for this narrative.

3. Test Recipe I

We may sprinkle our testing stuff with a few recipes for often occurring tests. This is the the first installment.

3.1. Test Equals and hashCode

Equals and hashCode are not twins in the direct sense, but indeed methods whose implementation should have a very direct connection. From the java Object API follows that:

  • Two objects that are equal by their equal method, than their hashCode should also be equal.

  • Note that the reverse is not true. If two hashCode are the same, that does not imply that the objects are equal.

  • A good hashCode 'spreads' the objects well, but this is not a very strict requirement or a requirement that can be enforced. A poor hashCode will lead to poor Hash{Map|Set} lookup performance.

Although hashCodes are invented to speedup finding object in a hash map or hash set, these collections use hashCode in the first part of the search, but must verify equality as last step.

The thing is that the equals method must consider quite a few things, expressed with conditional evaluation (if-then-else). The good thing is an IDE typically provides a way to generate equals and hashCode for you and these implementations are typically of good quality. But in particular the equals method there are quite some ifs, sometimes in disguise, coded as &&, so this will throw some flies in your coverage ointment.

However, we can balance this generated code by a piece of reusable test code, that can be used for almost all cases. In fact we have not found a case where it does not apply.

Let us first explain the usage and then the implementation.

Suppose you have an object with three fields, name, birthDate and id. All these fields should be considered in equals and hashCode.

As an exercise, create such and object now in your IDE, call it Student, why not.

class Student {
  final String name;
  final LocalDate birthDate;
  final int id;
}

From the above, the IDE can generate a constructor, equals and hashCode and toString. What are you waiting for? Because it is not test driven? You would be almost right, but why test drive something that can be generated. However, if your spec does not demand equals and hashCode, then do not write/generate such. That would be unwanted code. But if the requirements DO insist on equals and hashCode, make sure that the fields to be considered match the requirements.

After having such a generated equals and hashCode you have the predicament of writing a test. HashCode is relatively easy. It should produce an integer, but what value is unspecified, so just invoking it would do the trick for test coverage. The devil is in the equals details, because it has to consider:

  • Is the other object this? If yes, return true.

  • Is the other object null? Return false if it is.

  • Now consider the type.[1].

    • Is the other of the same type as this? If not return false.

    • Now we are in known terrain, the other is of the same type, so has the same fields.
      For each field test it this.field.equals(other.field). If not return false.

    • Using Objects.equals(this.fieldA, other.fieldB) can be an efficient to avoid testing for null on either field.

Generated equals. It is fine.
    @Override
    public boolean equals( Object obj ) {
        if ( this == obj ) {
            return true;
        }
        if ( obj == null ) {
            return false;
        }
        if ( getClass() != obj.getClass() ) {
            return false;
        }
        final Student other = (Student) obj;
        if ( this.id != other.id ) {
            return false;
        }
        if ( !Objects.equals( this.name, other.name ) ) {
            return false;
        }
        return Objects.equals( this.birthDate, other.birthDate );
    }

You see a pattern a pattern here: The number of ifs is 3 + the number of fields.

To test this, and to make sure you hit all code paths, you need to test with this, with null, with an distinct (read newly constructed) object with all fields equal, and then one for each field, which differs from the reference object only in said field.

Define those instances (for this example) as follows.

Instances for complete equals and hashCode test and coverage
    //@Disabled
    @Test
    void testEqualsAndHash() {
        Student ref = new Student( "Jan", LocalDate.of( 1999, 03, 23 ), 123 );
        Student equ = new Student( "Jan", LocalDate.of( 1999, 03, 23 ), 123 );
        Student sna = new Student( "Jen", LocalDate.of( 1999, 03, 23 ), 123 ); (1)
        Student sda = new Student( "Jan", LocalDate.of( 1998, 03, 23 ), 123 ); (2)
        Student sid = new Student( "Jan", LocalDate.of( 1999, 03, 23 ), 456 ); (3)
        verifyEqualsAndHashCode( ref, equ, sna, sda, sid );
        //fail( "testMethod reached it's and. You will know what to do." );
    }
1 Differs in name.
2 Differs in birthdate (year).
3 Differs in id.

The implementation of the verifyEqualsAndHashCode has been done with generics and a dash of AssertJ stuff.

Sample test helper in separate class.
package et;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.assertj.core.api.Assertions.*;

/**
 * Test helper.
 *
 * @author Richard van den Ham {@code r.vandenham@fontys.nl}
 * @author Pieter van den Hombergh {@code p.vandenhombergh@fontys.nl}
 */
public class TestHelpers {

    /**
     * Helper for equals tests, which are tedious to get completely covered.
     *
     * @param <T> type of class to test
     * @param ref reference value
     * @param equal one that should test equals true
     * @param unEqual list of elements that should test unequal in all cases.
     */
     public static <T> void verifyEqualsAndHashCode( T ref, T equal, T... unEqual ) {
         Object object = "Hello";
         T tnull = null;
         String cname = ref.getClass().getCanonicalName();
         // I got bitten here, assertJ equalTo does not invoke equals on the
         // object when ref and 'other' are same.
         // THAT's why the first one differs from the rest.
         assertThat( ref.equals( ref ) )
                 .as( cname + ".equals(this): with self should produce true" )
                 .isTrue();
         assertThat( ref.equals( tnull ) )
                 .as( cname + ".equals(null): ref object "
                         + safeToString( ref ) + " and null should produce false"
                 )
                 .isFalse();
         assertThat( ref.equals( object ) )
                 .as( cname + ".equals(new Object()): ref object"
                         + " compared to other type should produce false"
                 )
                 .isFalse();
         assertThat( ref.equals( equal ) )
                 .as( cname + " ref object [" + safeToString( ref )
                         + "] and equal object [" + safeToString( equal )
                         + "] should report equal"
                 )
                 .isTrue();
         for ( int i = 0; i < unEqual.length; i++ ) {
             T ueq = unEqual[ i ];
             assertThat( ref )
                     .as("testing supposed unequal objects")
             .isNotEqualTo( ueq );
         }
         // ref and equal should have same hashCode
         assertThat( ref.hashCode() )
                 .as( cname + " equal objects "
                         + ref.toString() + " and "
                         + equal.toString() + " should have same hashcode"
                 )
                 .isEqualTo( equal.hashCode() );
     }

    /**
     * ToString that deals with any exceptions that are thrown during its
     * invocation.
     *
     * When x.toString triggers an exception, the returned string contains a
     * message with this information plus class and system hashCode of the
     * object.
     *
     * @param x to turn into string or a meaningful message.
     * @return "null" when x==null, x.toString() when not.
     */
    public static String safeToString( Object x ) {
        if ( x == null ) {
            return "null";
        }
        try {
            return x.toString();
        } catch ( Throwable e ) {
            return "invoking toString on instance "
                    + x.getClass().getCanonicalName() + "@"
                    + Integer.toHexString( System.identityHashCode( x ) )
                    + " causes an exception " + e.toString();

        }
    }
}

The above code has been used before but now adapted for AssertJ and JUnit 5.

It is of course best to put this in some kind of test helper library, so you can reuse it over and over without having to resort to copy and waste.

4. Slides

5. Additional pointers

6. Exercises week 4

Exercise 1: Box of things

A Box for many things Before you learnt that generics exist, you had to get by. You needed classes that store information for you and let you retrieve it, too. So after a semester of Java, you have a bunch of classes in which you store stuff. For example, you have the class IntegerBox:

public class IntegerBox {
  private Integer value;

  public void putIntegerInBox(Integer anInt) {
    value = anInt;
  }
  public Integer getInteger() {
    return value;
  }
}

This little box class is pretty straight-forward. It has an Integer field at which you store the Integer that you put in the box, and you have a getter (getInteger) and a setter (putIntegerInBox) method to store and retrieve your Integers.

The trouble is, this box can’t deal with Double. Or String. Or anything that is not an Integer. So calling myBox.putIntegerInBox("Text") will not work. That means that for every new kind of variable, you need a new class. Quickly, your list of classes grows: IntegerBox, StringBox, DoubleBox, ListBox, StudentBox, BoxBox…​ this is very inefficient.

This is where you and Java generics come in:

  • Create a class named Box that can take any type using a type parameter.

  • Inside that class, have a private field that stores the same any type.

  • Create getter and setter methods for the class

Test your implementation by using this main method.
public static void main(String[] args) {

   Box<Integer> iBox = new Box<Integer>();
   Box<String> sBox = new Box<String>();

   iBox.putIn(5);
   sBox.putIn("Some Text");

   System.out.println("My integerBox contains: "+ iBox.getStored());
   System.out.println("My stringBox contains: "+ sBox.getStored());
}

To make the exercise complete, also test and implement Box.equals(Object o)` and hashCode().

ExtraChallenge EXTRA CHALLENGE: Create a class called Pair, that can take two different type parameters, store them in two separate fields and return them as well. Also test and implement equals and hashCode.

Exercise 2: Shape Filter

Shape Filters The shapes project template is in your repository, wk04.

In package shapes.basic you have multiple shapes, among which are:

  • Disk which extends Circle. Disk contains an image.

  • Sphere which extends Disk. Sphere creates a 3D like image of a sphere or Ball.

The essential detail here is that Disk and Sphere are sub classes of Circle. There is no need to change anything in this package. Just check if you understand the inheritance hierarchy and the use of interfaces and abstract classes.

Shape filter This part is about generics. Remember the PECS acronym. In the ShapeManager test-driven develop two methods:

  • void addShapes(Collection …​ shapesToAdd) Make sure to use the proper Collection definition with generics. The idea is that each collection of shapes (e.g. a collection of Circles, a collection of Disks, a collection of Shapes can be processed).

  • boolean collectShapesOfType( Collection<…​> consumer, Class<…​> type ) This method takes two methods, a collection (like a list) in which you can put shapes and the type of shape you want filter. For example, you’re passing an empty list that is filled with all Circles.


6.1. Test run reports Week 4


1. Not all equals implementation look at the type of this, See the java.util.List doc for a counter example