Skip to content

Testing and Benchmarking

Testing searches for defects and documents examples of expected behavior. Correctness proofs establish claims over a model. Neither replaces the other.

Test dimensions

  • representative examples;
  • boundary cases: empty, singleton, duplicates, extremes;
  • invalid inputs and exception contracts;
  • properties that hold across generated inputs;
  • regression cases for previously discovered defects;
  • integration behavior at real boundaries.

For sorting, useful properties include nondecreasing output, preservation of length and multiset, idempotence, and agreement with a trusted oracle.

@ParameterizedTest
@MethodSource("arrays")
void insertionSortMatchesTheJdk(int[] input) {
    int[] expected = input.clone();
    Arrays.sort(expected);
    int[] actual = input.clone();
    insertionSort(actual);
    assertArrayEquals(expected, actual);
}

Unit, integration, and system scope

A unit test isolates a small contract and runs quickly. An integration test checks collaboration with a real boundary such as a database or HTTP server. A system test observes the deployed application externally. Test doubles are useful at selected boundaries but cannot establish that a real integration works. See test doubles, contract tests, and property-based testing for their distinct guarantees and failure modes.

Benchmarking Java

Use JMH or an equivalent harness. A sound report states inputs, warm-up, measurement iterations, forks, JVM, hardware, statistical result, and uncertainty. Prevent dead-code elimination and avoid combining setup work with the operation under measurement.

System.nanoTime() is a clock primitive, not a benchmarking methodology. JIT compilation, garbage collection, adaptive optimization, CPU caches, and operating system noise all distort naive one-shot measurements.

For investigations outside a controlled microbenchmark, follow the debugging, profiling, and memory-analysis workflow.