2.3. Unit Testing

Robustness tests.

Auxiliary Method

Let us create PriorityQueueTest and define testRobustnessAux() that checks the robustness of any PQ inheriting AbstractPriorityQueue:

/**
 * @param pq   a priority queue.
 * @param keys a list of comparable keys.
 * @param comp a comparator used for sorting.
 */
<T extends Comparable<T>> void testRobustness(AbstractPriorityQueue<T> pq, List<T> keys, Comparator<T> comp) {
    keys.forEach(pq::add);
    keys = keys.stream().sorted(comp).collect(Collectors.toList());
    keys.forEach(key -> assertEquals(key, pq.remove()));
}
  • L6: the generic type T is defined specifically for this method such that the priority queue pq, the list keys, and the comparator comp take the same type T that is comparable.

  • L7: any collection that inherits the interface Iterable has the member method forEach() that takes a Consumer and applies each key in the collection to the consumer.

  • L8: any collection has the member method stream() that returns the Stream of the collection.

    • sorted(): creates a stream of the collection whose keys are sorted

    • collect(): creates a collection specified by the Collector.

    • toList(): returns a collector that transforms the stream into a list.

  • L9: iterates each of the sorted keys and compares it to the returned value of pq.remove().

What are the advantages of defining a method-level generic type?

Functional Programming

The following code shows a traditional way of iterating over a list (that is equivalent to L7 above):

for (int i=0; i<keys.size(); i++)
    pq.add(keys.get(i));

Java 5 introduced an enhanced for loop that simplified the traditional for loop:

for (T key : keys)
    pq.add(key);

Java 8 introduced lambda expressions that enabled functional programming in Java. The following code takes each key (as a variable) in keys and applies it to pq.add():

keys.forEach(key -> pq.add(key));

The syntax of the above code can be simplified as follows (as shown in L7):

keys.forEach(pq::add);

What are the main differences between object-oriented programming and functional programming?

Since Java 8, higher-order methods can be defined by parameterizing types of interfaces in the function package.

Test: Robustness

Let us define the testRobustness() method that calls the auxiliary method testRobustnessAux():

@Test
public void testRobustness() {
    List<Integer> keys = List.of(4, 1, 3, 2, 5, 6, 8, 3, 4, 7, 5, 9, 7);
    Comparator<Integer> natural = Comparator.naturalOrder();
    Comparator<Integer> reverse = Comparator.reverseOrder();

    testRobustness(new LazyPriorityQueue<>(), keys, reverse);
    testRobustness(new EagerPriorityQueue<>(), keys, reverse);
    testRobustness(new BinaryHeap<>(), keys, reverse);

    testRobustness(new LazyPriorityQueue<>(reverse), keys, natural);
    testRobustness(new EagerPriorityQueue<>(reverse), keys, natural);
    testRobustness(new BinaryHeap<>(reverse), keys, natural);
}
  • L7-9: tests different types of max-PQs. The keys need to be sorted in reverse order for it to test the remove() method.

  • L11-13: tests different types of min-PQs. The keys need to be sorted in the natural order for it to test the remove() method.

The generic types of the PQs in L4-5 are explicitly coded as <Integer>, whereas they are simplified to <> in L7-13. When do generic types need to be explicitly coded?

The testRobustness() method illustrates the benefit of defining AbstractPriorityQueue such that any type of PQ can be passed to testRobustnessAux() for unit-testing.

Last updated

©2023 Emory University - All rights reserved