使用 Java 8 Date/Time 类 编写和测试便捷方法

Writing and testing convenience methods using Java 8 Date/Time classes

我有一些旧的便利方法,使用日历编写,我想更新它们以使用 Java.time。* classes 在 Java 8. 中引入的一些方法在我的 class 中获取当前时间甚至当前小时的数量。

我计划为每种方法编写两种变体:一种假定时区是此计算机上定义的默认时区,另一种允许用户指定所需时区。

我想弄清楚两个主要问题:

  1. 如何在我的方法中获取当前时间戳。
  2. 如何使用当前时间戳的不同 来源对我的结果进行单元测试。

关于方法本身,我倾向于创建一个 ZonedDateTime 如下:

LocalDateTime currentDateTime = LocalDateTime.now();
ZoneId timeZoneDefault = ZoneId.systemDefault(); 
ZonedDateTime currentZonedDateTimeDefault = ZonedDateTime.of(currentDateTime, timeZoneDefault);

然后我会使用 DateTimeFormatter 来获取我真正关心的结果部分。例如,如果我想要 24 小时制的当前小时,然后是 12 小时制的当前小时,我会这样做:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("kk");
System.out.println("currentHour (24 hour clock): " + currentZonedDateTimeDefault.format(fmt));
DateTimeFormatter fmt2 = DateTimeFormatter.ofPattern("hh a");
System.out.println("currentHour (12 hour clock): " + currentZonedDateTimeDefault.format(fmt2));

到目前为止还可以吗?还是我应该使用不同的方法,也许使用 Instant?

我的主要问题是想出一种不同的方法来获取我的单元测试的当前时间,以便我可以将预期结果与实际结果进行比较。我一直在阅读有关 Clocks 和 Instants 的内容,而且我读得越多,就越感到困惑。有些方法似乎是从同一来源获取当前时间,因此它们可能会表明预期结果始终与实际结果相同——但两者都可能以同样的方式出错。

有人可以指导我计算给定时区当前时间的最佳方法,该时区以 不同 的方式获取时间,而不是我在我的方法中使用的方式吗?

更新: 我正在研究我的便捷方法,并在通过获取当前日期和时间开始的每个方法中使用它:

    LocalDateTime now = LocalDateTime.now();
    ZonedDateTime nowLocalTimeZone = ZonedDateTime.of(now, ZoneId.systemDefault());

然后,我得到了 date/time 中我真正想要的部分,代码如下:

try {
        DateTimeFormatter format = DateTimeFormatter.ofPattern(MONTH_NUMBER_FORMAT);
        return Integer.parseInt(nowLocalTimeZone.format(format));
    }
    catch (DateTimeParseException excp) {
        throw excp;         
    }

请注意,在这些方法中,所有内容都基于 LocalDateTime.now(),未指定时钟。

在我的单元测试中,我将其作为 class 变量:

    private Clock localClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());

在典型的单元测试中,我有这样的代码:

@Test
@DisplayName ("getCurrentMonthNumberLocal()")
void testGetCurrentMonthNumberLocal() {

    LocalDateTime now = LocalDateTime.now(localClock);
    ZonedDateTime nowLocalTimeZone = ZonedDateTime.of(now, ZoneId.systemDefault());

    //Normal cases
    assertEquals(nowLocalTimeZone.getMonthValue(), CurrentDateTimeUtils.getCurrentMonthNumberLocal());
}

请注意,此方法涉及使用名为 localClock 的固定时钟作为预期结果的来源。

当我 运行 测试时,预期结果与实际结果相匹配(至少对于我 运行 到目前为止的几个 classes,它寻找年份和月数)。

这种方法是否确保我从两个不同的来源获取当前时间以便比较有效?或者我是否有效地从完全相同的来源获取了相同的信息,例如我计算机上的系统时钟?

我非常希望我从这些方法中获得的 date/time 来自不同的来源,而不是 date/time 用于确定单元测试中预期结果的来源,以便我确定它是一个有效的测试,否则,我认为测试这些方法没有多大意义。 (我想我可以只显示每个函数的结果,看看它是否大致正确。很容易判断年、月和日是否正确,除非你可能非常接近那年、月或天,在这种情况下,您实际上可能会看到不同时区的值,但您并不知道。)

在您的测试中(并且仅在测试期间!)设置您方便 class 使用的时钟,以便您可以独立于计算机时钟预测 desired/expected 结果:

public class ConvenienceTest {

    @Test
    public void testGetLocalHour() {
        Convenience underTest = new Convenience();

        ZoneId zone = ZoneId.of("America/Los_Angeles");

        ZonedDateTime fixedZdt = ZonedDateTime.now(zone).withHour(0);
        underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
        assertEquals("24", underTest.getLocalHour24HourClock());

        fixedZdt = fixedZdt.withHour(1);
        underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
        assertEquals("01", underTest.getLocalHour24HourClock());

        fixedZdt = fixedZdt.withHour(23);
        underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
        assertEquals("23", underTest.getLocalHour24HourClock());

        // TODO test with other time zones
    }

}

要实现这一点,当然需要您方便 class 可以接受 Clock 并使用它:

public class Convenience {

    private Clock clock = Clock.systemDefaultZone();

    /** For testing only. Sets the clock from which to get the time. */
    void setClock(Clock clockForTest) {
        this.clock  = clockForTest;
    }

    public String getLocalHour24HourClock() {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("kk");
        ZonedDateTime zdt = ZonedDateTime.now(clock);
        return zdt.format(fmt);
    }

}

通过这个实现,测试刚刚在我的计算机上通过。

退一步说:如果您的便利方法只是 java.time 之上的薄层,您可能会开始考虑单元测试有多大价值。如果它正在做一些真正的工作(比如上面例子中的格式化,一些经常出错的东西),测试是有价值的,但是如果一个方法只是 returns 来自对 java.time 的单个调用的值,您可能不需要测试。你不应该测试 java.time,最好只测试你自己的代码。