Functional Programming in Java, Second Edition: Functional Programming in Java, Second Edition: JUnit code improvements for Chapter 11, pages 185 ff “Refactoring Unbounded Loops”

The same remarks apply as for “Refactoring the Traditional for Loop”

  • No init()
  • Negative tests which apply for input less than 1900, resulting in exceptions.

but we cannot implement a common interface as the method parameters change. So we wrap the new class into a class that obeys the old interface, which agains llaows us to use the same test code to test both odl and new classes.

There is also a little tweak to the semantics of the case where 1900 is already rejected - instead of returning 0, we throw. Because there basically is no result in that case.

package chapter11; import org.junit.jupiter.api.Test; import java.time.Year; import java.util.function.Predicate; import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; public class UnboundedLoopTest { interface Continue { boolean check(int year); } interface WithContinue { int countFrom1900(final Continue shouldContinue); } static class LeapYearsUnboundedBefore implements WithContinue { public int countFrom1900(final Continue shouldContinue) { if (!shouldContinue.check(1900)) { throw new IllegalArgumentException("Cannot 'continue' right at the start!'"); } int count = 0; for (int year = 1900; ; year += 4) { if (!shouldContinue.check(year)) { break; } if (Year.isLeap(year)) { count++; } } return count; } } // Does NOT implement WithContinue!! static class LeapYearsUnboundedAfter { public int countFrom1900(final Predicate<Integer> shouldContinue) { if (!shouldContinue.test(1900)) { throw new IllegalArgumentException("Cannot 'continue' right at the start!'"); } return (int) IntStream.iterate(1900, year -> year + 4) .takeWhile(shouldContinue::test) .filter(Year::isLeap) .count(); } } static class LeapYearsUnboundedAfterWrapped implements WithContinue { private final LeapYearsUnboundedAfter ly; public LeapYearsUnboundedAfterWrapped(LeapYearsUnboundedAfter ly) { this.ly = ly; } public int countFrom1900(final Continue shouldContinue) { return ly.countFrom1900(year -> shouldContinue.check(year)); } } // One should maybe have a separate method to test the throws private static void commonLeapYearsTests(final WithContinue withContinue) { assertAll( () -> assertEquals(0, withContinue.countFrom1900(year -> year <= 1900)), () -> assertEquals(25, withContinue.countFrom1900(year -> year <= 2000)), () -> assertEquals(27, withContinue.countFrom1900(year -> year <= 2010)), () -> assertEquals(31, withContinue.countFrom1900(year -> year <= 2025)), () -> assertEquals(49, withContinue.countFrom1900(year -> year <= 2100)), () -> assertEquals(267, withContinue.countFrom1900(year -> year <= 3000)), () -> assertThrows(IllegalArgumentException.class, () -> withContinue.countFrom1900(year -> year < 1800) ), () -> assertThrows(IllegalArgumentException.class, () -> withContinue.countFrom1900(year -> year < 1900) ) ); } @Test void leapYearCountBefore() { commonLeapYearsTests(new LeapYearsUnboundedBefore()); } @Test void leapYearCountAfter() { commonLeapYearsTests(new LeapYearsUnboundedAfterWrapped(new LeapYearsUnboundedAfter())); } }