@Test
public void shouldScheduleWithJitter() {

  // Set up a runnable that records execution timings
  var executionTimes = new ArrayList<Integer>();
  var time = new AtomicInteger();
  Runnable runnable = () -> executionTimes.add(time.get());
  // Schedule the runnable on an executor we can control
  scheduleWithJitter(runnable, SCHEDULER, INTERVAL);
  // Simulate running it for 10k seconds
  for (int i = 0; i < 10000; i++) {
    time.set(i);
    SCHEDULER.tick(1, SECONDS);
  }
  // Calculate timing statistics
  var statistics = IntStream.range(0, executionTimes.size() - 1)
      .map(i -> executionTimes.get(i + 1) - executionTimes.get(i))
      .summaryStatistics();
  assertThat(statistics.getAverage(), is(closeTo(INTERVAL.getSeconds(), 3)));
  assertThat((double) statistics.getMin(), is(
      both(greaterThanOrEqualTo(INTERVAL.getSeconds() * (1.0 - JITTER)))
          .and(lessThanOrEqualTo(INTERVAL.getSeconds() * 1.0))));
  assertThat((double) statistics.getMax(), is(
      both(greaterThanOrEqualTo(INTERVAL.getSeconds() * 1.0))
          .and(lessThanOrEqualTo(INTERVAL.getSeconds() * (1.0 + JITTER)))));
}