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

时间:2018-10-28 21:17:48

标签: 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);
}

这用于计算自11月开始的索偿年度。

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

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

有没有简单的解决方案可以对此进行测试?

1 个答案:

答案 0 :(得分:2)

java.time

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

以下是我替换您的代码。

自定义课程

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

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

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

该课程紧随java.time.Year课程的领导,包括its naming conventions

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

示例用法:

AcademicYear.now().getDisplayName() // 2017-2018

AcademicYear.now().getValueStart() // First year of the two-year range, such as 2018 of the year 2018-2019.

我把这堂课放在一起,没有测试。因此,请以此为指导,而不是生产代码。

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;
    }

}

ZoneId

另一个更改是时区ZoneId的必需参数。

时区对于确定日期(因此决定年份)至关重要。在任何给定时刻,日期都会在全球范围内变化。例如,Paris France午夜之后的几分钟是新的一天,而Montréal Québec仍然是“昨天”。

如果未指定时区,则JVM隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将desired/expected time zone明确指定为参数。

continent/region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用2-4个字母的缩写,例如ESTIST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

破坏方法

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

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

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

Clock用于单元测试

对于测试,您可以将Clock实例传递给now方法变体之一,该实例可以根据需要更改时间。 Clock类为多个时钟提供了更改的行为,例如定点时间,当前时刻加/减时间跨度以及更改的节奏,其中时钟按您指定的粒度滴答(例如每5分钟) 。

有关更改的Clock行为的更多信息,请参见my Answer到另一个问题。

示例:

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 ) ;

关于 java.time

java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendarSimpleDateFormat

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

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

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore