【问题标题】:Time-independent JUnit Test Cases与时间无关的 JUnit 测试用例
【发布时间】:2016-01-05 19:19:53
【问题描述】:

我有一个使用 joda-time 计算年龄的方法的 Util 类:

public static int getAge(LocalDate birthdate) {
    LocalDate today = new LocalDate();
    Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
    return period.getYears();
}

我已经编写了一个广泛的 JUnit 测试类,但是如何使它与时间无关?如果我创建一个测试用例来计算某人的年龄,如果他们今天出生于 1970 年,那么结果将是 46 岁。但是 1 年后,如果我运行测试用例,它会失败,因为结果是 47。

那么我如何使这些测试用例与时间无关。我正在考虑使用某种日历接口,测试用例将从中创建日期对象。我还偶然发现了this 帖子,这是另一种可能的解决方案,但我不确定如何去做。

【问题讨论】:

    标签: java unit-testing date junit jodatime


    【解决方案1】:

    一种常见的做法(在《Growing Object Oriented Software by Tests》一书中描述)是将创建日期实例的责任委托给协作者。

    想象一下你有一个这样的课程:

    public class Clock {
      public LocaleDate now(){
        return  new LocalDate();
      }
    }
    

    那么你的代码可能会变成

    public static int getAge(LocalDate birthdate, Clock clock) {
        LocalDate today = clock.now();
        Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
        return period.getYears();
    }
    

    我可以不测试Clock#now,因为它非常正确,但我现在可以传入一个模拟时钟实例来测试getAge 方法。

    现在这意味着使用静态getAge 的类必须提供时钟,时钟将是此类的依赖项。可以以各种可模拟的方式注入此类依赖项。

    我个人属于injection by constructor 学校,但是从injection by reflection 到受保护的工厂方法(因此可以在用于创建测试实例的匿名子类中重载)的任何东西都有效。

    这个问题和我建议的设计在the book、许多blog postsstack overflow 中都进行了详细讨论(如果你愿意迁移到java,java 8 似乎有direct support。时间)

    【讨论】:

      【解决方案2】:

      使用固定日期:

          LocalDate today = new LocalDate(2016, 1, 5);
      

      【讨论】:

      • 哇......我不知道为什么我没有想到这个......最明显的解决方案......谢谢
      • 实际上这不起作用,因为我现在考虑它,因为new LocalDate 在 getAge 函数内部,我不能在那里硬编码一个值。在生产使用时,它必须是当前日期。所以我想知道是否有某种方法可以使用Mockito.when 替换它
      • 你可以很好地使用Mockito.when。或者,如果允许,您可以覆盖 getAge 方法以返回测试用例的静态日期。
      • 我宁愿走Mockito.when 路线,但我不确定在这种情况下如何实现。
      【解决方案3】:

      所以,首先让我说我同意Jean。但是我发现这种模式非常普遍,我在 GitHub 上的一个名为 LibEx 的库中实现了一个可重用的解决方案。它被称为DateSupplier。要获取当前的DateTime,请使用DateSupplier.getCurrentDateTime()。然后在测试中使用@Rule DateController,它允许datController.setCurrentDateTo...()。在测试中设置时,DateSupplier 将返回由DateController 指定的值。

      以下是链接:

      您的测试将如下所示..

      public class DateControllerTest extends TestBase {
      
      @Rule
      public DateController dateContoller = new DateController();
      
      @Test
      public void testSetCurrentTime() {
          // setup
          Date date = new Date(123443);
      
          // test
          dateContoller.setCurrentTime(date);
      
          // verify
          assertDateEquals(date);
      }
      
      private void assertDateEquals(Date date) {
          assertThat(DateSupplier.getCurrentDate(), equalTo(date));
          assertThat(DateSupplier.getCurrentDateInMilliseconds(), equalTo(date.getTime()));
      }
      

      因为DateControllerRule,所以每次测试后都会重置为默认行为。此外,DateController 位于应测试范围的 libex-test 模块中,因此它已部署在您的生产代码中。

      【讨论】:

        【解决方案4】:

        所以你想知道某人今天 46 岁的生日吗? (你的问题不清楚。)

        java.time

        Joda-Time 的制造商告诉我们,只要您觉得舒服,就转而使用 Java 8 和更高版本中内置的 java.time 框架。

        ZonedDateTime birthdateOf46YearOld = ZonedDateTime.now().minusYears( 46 );
        

        这个对象被隐式分配给 JVM 当前的默认时区。我建议始终明确您的时区。

        ZoneId zoneId = ZoneId.of( "America/Montreal" );  // Or… ZoneOffset.UTC …or… ZoneId.systemDefault().
        ZonedDateTime birthdateOf46YearOld = ZonedDateTime.now( zoneId ).minusYears( 46 );
        

        或者您想测试手头的日期时间值是否与该生日相同?

        Boolean hit = someZdt.isEqual( birthdateOf46YearOld );
        

        如果您只关心没有时间或时区的日期,请使用LocalDate,类似于 Joda-Time。请注意,时区对于确定今天的日期至关重要,因为它在全球各地的时区中有所不同。

        LocalDate birthdate = LocalDate.now( zoneId ).minusYears( 46) ;
        

        如果您只是想要一对 LocalDate 对象之间经过的时间(以年为单位),请使用 Period 类。

        LocalDate today = LocalDate.now( zoneId );
        LocalDate birthDate = … ;
        Period period = Period.between( birthDate , today );
        Integer ageInYears = period.getYears();
        Boolean hit = ageInYears.equals ( Integer.valueOf( 46 ) ) ;    
        

        乔达时间

        如果您无法使用 Java 8 或更高版本,则在 Joda-Time 中可以使用相同类型的代码。

        DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
        DateTime birthdate = DateTime.now( zone ).minusYears( 46 );
        

        【讨论】:

        • 亲爱的投票者:请在投票的同时留下批评意见。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-08
        • 2013-02-10
        • 2014-09-24
        • 2020-03-20
        • 1970-01-01
        相关资源
        最近更新 更多