Skip to content

Commit

Permalink
ENH: NAV-14 - Improve testing of GTFS schedule
Browse files Browse the repository at this point in the history
- Create larger example schedule with more calendar variations and both directions.
- Increase test coverage concerning boundary cases in next departures (midnight), nearest stops (same location query) and active trips (dates outside validity or no service day).
  • Loading branch information
munterfi committed May 4, 2024
1 parent 45921e3 commit 2b1cdc2
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
public final class ServiceDayTime implements Comparable<ServiceDayTime> {
private final int totalSeconds;

public ServiceDayTime(int seconds) {
this.totalSeconds = seconds;
}

public ServiceDayTime(int hours, int minutes, int seconds) {
this.totalSeconds = seconds + 60 * minutes + 3600 * hours;
}
Expand Down
154 changes: 133 additions & 21 deletions src/test/java/ch/naviqore/gtfs/schedule/model/GtfsScheduleTest.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package ch.naviqore.gtfs.schedule.model;

import ch.naviqore.gtfs.schedule.type.ServiceDayTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
Expand All @@ -17,8 +20,8 @@ class GtfsScheduleTest {

@BeforeEach
void setUp(GtfsScheduleTestBuilder builder) {
schedule = builder.withAddAgency().withAddCalendars().withAddStops().withAddRoutes().withAddTrips()
.withAddStopTimes().build();
schedule = builder.withAddAgency().withAddCalendars().withAddCalendarDates().withAddInterCity()
.withAddUnderground().withAddBus().build();
}

@Nested
Expand All @@ -36,12 +39,12 @@ void shouldCorrectlyCountRoutes() {

@Test
void shouldCorrectlyCountStops() {
assertThat(schedule.getStops()).hasSize(5);
assertThat(schedule.getStops()).hasSize(9);
}

@Test
void shouldCorrectlyCountTrips() {
assertThat(schedule.getTrips()).hasSize(3);
assertThat(schedule.getTrips()).hasSize(671);
}

@Test
Expand All @@ -54,36 +57,122 @@ void shouldCorrectlyCountCalendars() {
class NearestStops {

@Test
void shouldFindStopsWithin500Meters() {
assertThat(schedule.getNearestStops(47.3769, 8.5417, 500)).hasSize(3).extracting("id")
.containsOnly("stop1", "stop2", "stop3");
void shouldFindStopWithin1Meter() {
assertThat(schedule.getNearestStops(47.5, 8.5, 1)).hasSize(1).extracting("id").containsOnly("s2");
}

@Test
void shouldFindStopsWithin10000Meters() {
assertThat(schedule.getNearestStops(47.5, 8.5, 10000)).hasSize(3).extracting("id")
.containsOnly("u6", "s2", "u3");
}

@Test
void shouldFindAllStops() {
assertThat(schedule.getNearestStops(47.5, 8.5, Integer.MAX_VALUE)).hasSize(9).extracting("id")
.containsOnly("s1", "s2", "s3", "u1", "u2", "u3", "u4", "u5", "u6");
}

@Test
void shouldFindNoStopsWhenNoneAreCloseEnough() {
assertThat(schedule.getNearestStops(47.3800, 8.5500, 100)).isEmpty();
assertThat(schedule.getNearestStops(47.6, 8.5, 100)).isEmpty();
}
}

@Nested
class NextDepartures {

private static final String STOP_ID = "s2";
private static final int LIMIT = 5;

private static void assertWeekendAndHoliday(List<StopTime> departures) {
// assert departures times are correct
List<ServiceDayTime> expectedDepartures = List.of(ServiceDayTime.parse("08:15:00"),
ServiceDayTime.parse("08:15:00"), ServiceDayTime.parse("09:15:00"),
ServiceDayTime.parse("09:15:00"), ServiceDayTime.parse("10:15:00"));
assertThat(departures).hasSize(LIMIT).extracting(StopTime::departure)
.containsExactlyElementsOf(expectedDepartures);

// assert trips are correct
List<String> expectedTripIds = List.of("route1_we_f_4", "route1_we_r_4", "route1_we_f_5", "route1_we_r_5",
"route1_we_f_6");
List<String> tripIds = departures.stream().map(stopTime -> stopTime.trip().getId()).toList();
assertThat(tripIds).containsExactlyElementsOf(expectedTripIds);

// assert routes are correct
Set<String> expectedRouteIds = Set.of("route1");
List<String> routeIds = departures.stream().map(stopTime -> stopTime.trip().getRoute().getId()).toList();
assertThat(routeIds).allMatch(expectedRouteIds::contains);
}

@Test
void shouldReturnNextDeparturesOnWeekday() {
assertThat(schedule.getNextDepartures("stop1", GtfsScheduleTestBuilder.Moments.WEEKDAY_8_AM,
Integer.MAX_VALUE)).hasSize(1);
List<StopTime> departures = schedule.getNextDepartures(STOP_ID,
GtfsScheduleTestBuilder.Moments.WEEKDAY_8_AM, LIMIT);

// assert departures times are correct
List<ServiceDayTime> expectedDepartures = List.of(ServiceDayTime.parse("08:00:00"),
ServiceDayTime.parse("08:00:00"), ServiceDayTime.parse("08:09:00"),
ServiceDayTime.parse("08:09:00"), ServiceDayTime.parse("08:15:00"));
assertThat(departures).hasSize(LIMIT).extracting(StopTime::departure)
.containsExactlyElementsOf(expectedDepartures);

// assert trips are correct
List<String> expectedTripIds = List.of("route3_wd_f_16", "route3_wd_r_16", "route3_wd_f_17",
"route3_wd_r_17", "route1_wd_f_7");
List<String> tripIds = departures.stream().map(stopTime -> stopTime.trip().getId()).toList();
assertThat(tripIds).containsExactlyElementsOf(expectedTripIds);

// assert routes are correct
Set<String> expectedRouteIds = Set.of("route1", "route3");
List<String> routeIds = departures.stream().map(stopTime -> stopTime.trip().getRoute().getId()).toList();
assertThat(routeIds).allMatch(expectedRouteIds::contains);
}

@Test
void shouldReturnNoNextDeparturesOnWeekday() {
assertThat(schedule.getNextDepartures("stop1", GtfsScheduleTestBuilder.Moments.WEEKDAY_9_AM,
Integer.MAX_VALUE)).isEmpty();
void shouldReturnNextDeparturesOnWeekend() {
List<StopTime> departures = schedule.getNextDepartures(STOP_ID,
GtfsScheduleTestBuilder.Moments.WEEKEND_8_AM, LIMIT);

assertWeekendAndHoliday(departures);
}

@Test
void shouldReturnNextDeparturesOnSaturday() {
assertThat(schedule.getNextDepartures("stop1", GtfsScheduleTestBuilder.Moments.SATURDAY_9_AM,
Integer.MAX_VALUE)).hasSize(1);
void shouldReturnNextDeparturesOnHoliday() {
List<StopTime> departures = schedule.getNextDepartures(STOP_ID,
GtfsScheduleTestBuilder.Moments.HOLIDAY.atTime(8, 0), LIMIT);

assertWeekendAndHoliday(departures);
}

@Test
void shouldReturnNextDeparturesAfterMidnight() {
List<StopTime> departures = schedule.getNextDepartures(STOP_ID,
GtfsScheduleTestBuilder.Moments.WEEKDAY_12_PM, LIMIT);

// assert departures times are correct
List<ServiceDayTime> expectedDepartures = List.of(ServiceDayTime.parse("24:00:00"),
ServiceDayTime.parse("24:00:00"), ServiceDayTime.parse("24:09:00"),
ServiceDayTime.parse("24:09:00"), ServiceDayTime.parse("24:15:00"));
assertThat(departures).hasSize(LIMIT).extracting(StopTime::departure)
.containsExactlyElementsOf(expectedDepartures);

// assert trips are correct
List<String> expectedTripIds = List.of("route3_wd_f_80", "route3_wd_r_80", "route3_wd_f_81",
"route3_wd_r_81", "route1_wd_f_39");
List<String> tripIds = departures.stream().map(stopTime -> stopTime.trip().getId()).toList();
assertThat(tripIds).containsExactlyElementsOf(expectedTripIds);

// assert routes are correct
Set<String> expectedRouteIds = Set.of("route1", "route3");
List<String> routeIds = departures.stream().map(stopTime -> stopTime.trip().getRoute().getId()).toList();
assertThat(routeIds).allMatch(expectedRouteIds::contains);
}

@Test
void shouldReturnNoNextDeparturesOnNoServiceDay() {
assertThat(schedule.getNextDepartures(STOP_ID, GtfsScheduleTestBuilder.Moments.NO_SERVICE.atTime(8, 0),
Integer.MAX_VALUE)).isEmpty();
}

@Test
Expand All @@ -96,21 +185,44 @@ void shouldReturnNoDeparturesFromUnknownStop() {
@Nested
class ActiveTrips {

private static void assertWeekendAndHoliday(List<Trip> activeTrips) {
assertThat(activeTrips).hasSize(168).extracting(trip -> trip.getRoute().getId())
.containsAll(Set.of("route1", "route2"));
}

@Test
void shouldReturnActiveTripsOnWeekday() {
assertThat(schedule.getActiveTrips(GtfsScheduleTestBuilder.Moments.WEEKDAY_8_AM.toLocalDate())).hasSize(2)
.extracting("id").containsOnly("trip1", "trip2");
List<Trip> activeTrips = schedule.getActiveTrips(
GtfsScheduleTestBuilder.Moments.WEEKDAY_8_AM.toLocalDate());

assertThat(activeTrips).hasSize(503).extracting(trip -> trip.getRoute().getId())
.containsAll(Set.of("route1", "route2", "route3"));
}

@Test
void shouldReturnActiveTripsOnWeekend() {
assertThat(schedule.getActiveTrips(GtfsScheduleTestBuilder.Moments.SATURDAY_9_AM.toLocalDate())).hasSize(1)
.extracting("id").containsOnly("trip3");
List<Trip> activeTrips = schedule.getActiveTrips(
GtfsScheduleTestBuilder.Moments.WEEKEND_8_AM.toLocalDate());

assertWeekendAndHoliday(activeTrips);
}

@Test
void shouldReturnActiveTripsOnHoliday() {
List<Trip> activeTrips = schedule.getActiveTrips(GtfsScheduleTestBuilder.Moments.HOLIDAY);

assertWeekendAndHoliday(activeTrips);
}

@Test
void shouldReturnNoActiveTripsForDaysOutsideValidity() {
assertThat(schedule.getActiveTrips(GtfsScheduleTestBuilder.Validity.PERIOD_START.minusDays(1))).isEmpty();
assertThat(schedule.getActiveTrips(GtfsScheduleTestBuilder.Validity.PERIOD_END.plusDays(1))).isEmpty();
}

@Test
void shouldReturnNoActiveTripsForNonServiceDay() {
assertThat(schedule.getActiveTrips(GtfsScheduleTestBuilder.Validity.END_DATE.plusMonths(1))).isEmpty();
assertThat(schedule.getActiveTrips(GtfsScheduleTestBuilder.Moments.NO_SERVICE)).isEmpty();
}
}
}
Loading

0 comments on commit 2b1cdc2

Please sign in to comment.