通过更改当前日期的 Java 测试日历

Tyv*_*ain 2 java junit

我有这个功能:

/**
 * @return From november to december -> current year +1 else current year
 */
public static int getCurrentScolYear () {
    int month = Calendar.getInstance().get(Calendar.MONTH);
    if (  month == NOVEMBER || month == DECEMBER) {
        return Calendar.getInstance().get(Calendar.YEAR) +1;
    }

    return Calendar.getInstance().get(Calendar.YEAR);
}
Run Code Online (Sandbox Code Playgroud)

这用于计算 scolar 年(从 11 月开始)。

我想通过更改“当前时间”在 Junit 测试中对此进行测试。所以我可以测试不同日期的结果。

我找到了这个线程,但它似乎过于复杂:java: how to mock Calendar.getInstance()?

有没有一个简单的解决方案来测试这个?

Bas*_*que 5

时间

可怕的Calendar类在几年前被java.time类取代,特别是ZonedDateTime. 没有理由Calendar再次使用。

下面是我替换你的代码。

自定义类

我们可以返回类型安全且不言自明的java.time.Year对象,而不仅仅是一个整数。但这会产生误导,因为该类被明确定义为符合 ISO 8601 的年份编号。

我建议你定义自己的AcademicYear类。传递此类的对象以使您的代码更具自文档性、提供类型安全性并确保有效值。类似于以下课程。

这个自定义类还为您的静态方法提供了一个家。

此类遵循java.time.Year该类的指导原则,包括其命名约定。

一个学年实际上是两年的数字,开始年份和下一个停止年份。下面的类存储两者。

示例用法:

AcademicYear.now().getDisplayName() // 2017-2018
Run Code Online (Sandbox Code Playgroud)

或者

AcademicYear.now().getValueStart() // First year of the two-year range, such as 2018 of the year 2018-2019.
Run Code Online (Sandbox Code Playgroud)

我在没有测试的情况下把这门课扔到了一起。所以使用它作为指导,而不是生产代码。

AcademicYear.java

package com.basilbourque.example;

import java.time.*;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;

// This class follows the lead of the `java.time.Year` class.
// See JavaDoc: https://docs.oracle.com/javase/10/docs/api/java/time/Year.html
// See OpenJDK source-code: http://hg.openjdk.java.net/jdk10/master/file/be620a591379/src/java.base/share/classes/java/time/Year.java
// This class follows the immutable objects pattern. So only getter accessor methods, no setters.
public class AcademicYear {
    // Statics
    static public int FIRST_YEAR_LIMIT = 2017;
    static public int FUTURE_YEARS_LIMIT = 10;
    static public Set < Month > academicYearStartingMonths = EnumSet.of( Month.NOVEMBER , Month.DECEMBER );

    // Members
    private int academicYearNumberStart;

    // Constructor
    private AcademicYear ( int academicYearNumberStart ) {
        if ( academicYearNumberStart < AcademicYear.FIRST_YEAR_LIMIT ) {
            throw new IllegalArgumentException( "Received a year number: " + academicYearNumberStart + " that is too long ago (before " + AcademicYear.FIRST_YEAR_LIMIT + "). Message # c5fd65c1-ed10-4fa1-96db-77d08ef1d97e." );
        }
        if ( academicYearNumberStart > Year.now().getValue() + AcademicYear.FUTURE_YEARS_LIMIT ) {
            throw new IllegalArgumentException( "Received a year number that is too far in the future, over " + AcademicYear.FUTURE_YEARS_LIMIT + " away. Message # 8581e4ca-afb3-4ab7-8849-9b02c434eb4c." );
        }
        this.academicYearNumberStart = academicYearNumberStart;
    }

    public static AcademicYear of ( int academicYearNumberStart ) {
        return new AcademicYear( academicYearNumberStart );
    }

    public int getValueStart ( ) {
        return this.academicYearNumberStart;
    }

    public int getValueStop ( ) {
        return ( this.academicYearNumberStart + 1 );
    }

    public String getDisplayName ( ) {
        String s = this.academicYearNumberStart + "-" + ( this.academicYearNumberStart + 1 );
        return s;
    }

    // ------| `Object`  |---------------------

    @Override
    public String toString ( ) {
        return "AcademicYear{ " +
                "academicYearNumberStart=" + academicYearNumberStart +
                " }";
    }

    @Override
    public boolean equals ( Object o ) {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        AcademicYear that = ( AcademicYear ) o;
        return this.getDisplayName().equals( that.getDisplayName() );
    }

    @Override
    public int hashCode ( ) {
        return Objects.hash( this.getDisplayName() );
    }

    // -----------|  Factory methods  |-------------------

    static public AcademicYear now ( ) {  // I think making ZoneId optional is a poor design choice, but I do so here to mimic `java.time.Year`.
        AcademicYear ay = AcademicYear.now( Clock.systemDefaultZone() );
        return ay;
    }

    static public AcademicYear now ( ZoneId zoneId ) {
        // Determine the current date as seen in the wall-clock time used by the people of a particular region (a time zone).
        AcademicYear ay = AcademicYear.now( Clock.system( zoneId ) );
        return ay;
    }

    static public AcademicYear now ( Clock clock ) {
        final LocalDate localDate = LocalDate.now( clock );
        AcademicYear ay = AcademicYear.from( localDate );
        return ay;
    }

    static public AcademicYear from ( LocalDate localDate ) {
        Objects.requireNonNull( localDate , "Received a null `LocalDate` object. Message # 558dd5e8-5cff-4c6e-b0f8-40dbcd76a753." );
        // Extract the month of the specified date. If not Nov or Dec, subtract one from the year.
        int y = localDate.getYear();
        // If not November or December, subtract 1.
        int startingYear;
        if ( ! academicYearStartingMonths.contains( localDate.getMonth() ) ) {
            startingYear = ( y - 1 );
        } else {
            startingYear = y;
        }
        AcademicYear ay = AcademicYear.of( startingYear );
        return ay;
    }

}
Run Code Online (Sandbox Code Playgroud)

ZoneId

另一个变化是ZoneId时区的必需参数。

时区对于确定日期至关重要,因此是确定年份。对于任何给定时刻,日期因地区而异。例如,在法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是“昨天” 。

如果未指定时区,JVM 会隐式应用其当前默认时区。该默认值可能会在运行时随时更改(!),因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。

以、、 或等格式指定正确的时区名称。永远不要使用 2-4 个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/regionAmerica/MontrealAfrica/CasablancaPacific/AucklandESTIST

分手方法

另一个变化是我们有两组方法而不是一个方法。

一种方法,AcademicYear.from负责进行 11 月调整的逻辑。您将 a 传递LocalDate给此方法。

其他方法, 的三种变体AcademicYear.now,负责确定当前时刻,调整到指定的时区,并提取仅日期LocalDate对象。也就是说LocalDate然后对象被传递到另一个方法。

Clock 用于单元测试

对于测试,您可以将now一个Clock实例传递给方法变体之一,该实例可以根据需要改变时间。本Clock类提供了几个时钟与行为改变,比如在固定的时间点,当前时刻加/减一个跨度的时间,和改变节奏在时钟在您指定的粒度蜱如每5分钟。

有关改变的Clock行为的更多信息,请参阅我对另一个问题的回答。

例子:

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 23 ) ;  // 2018-01-23
LocalTime lt = LocalTime.of( 8 , 0 ) ; // 8 AM.
Instant instant = ZonedDateTime.of( ld , lt , z ).toInstant() ;
Clock c = Clock.fixed( instant , z ) ;
AcademicYear ay = AcademicYear.now( c ) ;
Run Code Online (Sandbox Code Playgroud)

关于java.time

该java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat。

现在处于维护模式的Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310。

您可以直接与您的数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*

从哪里获得 java.time 类?

该ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如Interval,YearWeek,YearQuarter,和更多。