[java] 시간에 민감한 코드를 테스트하기 위해 Java System.currentTimeMillis를 재정의하십시오.

System.currentTimeMillis호스트 컴퓨터에서 시스템 시계를 수동으로 변경하는 것 외에 를 통해 제시된대로 코드에서 또는 JVM 인수로 현재 시간을 재정의하는 방법이 있습니까?

작은 배경 :

현재 날짜 (예 : 월 1 일, 1 년 1 일 등) 동안 많은 논리를 순환하는 여러 회계 작업을 실행하는 시스템이 있습니다.

불행하게도, 기존의 많은 코드는 다음과 같은 기능을 호출 new Date()또는 Calendar.getInstance()결국 아래로 전화 둘 다, System.currentTimeMillis.

테스트 목적으로 지금 당장 시스템 클럭을 수동으로 업데이트하여 테스트 실행 중 코드가 생각하는 시간과 날짜를 조작해야합니다.

그래서 내 질문은 :

에 의해 반환되는 것을 재정의하는 방법이 System.currentTimeMillis있습니까? 예를 들어, JVM이 해당 메소드에서 리턴하기 전에 오프셋을 자동으로 더하거나 빼도록 지시하려면?

미리 감사드립니다!



답변

내가 강하게 대신 시스템 시계와 장난, 당신은 글 머리 기호 및 레거시 코드가 교체 클럭을 사용하는 것을 리팩토링을 물린 것이 좋습니다. 이상적으로 는 의존성 주입으로 수행해야하지만 교체 가능한 싱글 톤을 사용하더라도 테스트 가능성을 얻을 수 있습니다.

이것은 싱글 톤 버전의 검색 및 대체로 거의 자동화 될 수 있습니다.

  • 교체 Calendar.getInstance()와 함께 Clock.getInstance().getCalendarInstance().
  • 교체 new Date()Clock.getInstance().newDate()
  • 교체 System.currentTimeMillis()Clock.getInstance().currentTimeMillis()

(필요에 따라 등)

첫 번째 단계를 수행 한 후에는 싱글 톤을 한 번에 조금씩 DI로 바꿀 수 있습니다.


답변

tl; dr

호스트 컴퓨터에서 시스템 시계를 수동으로 변경하는 것 외에 System.currentTimeMillis를 통해 표시되는 현재 시간을 코드 또는 JVM 인수로 사용하여 현재 시간을 재정의하는 방법이 있습니까?

예.

Instant.now(
    Clock.fixed(
        Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
    )
)

Clock java.time에서

가짜 날짜-시간 값으로 테스트를 용이하게하기 위해 플러그 가능 클록 교체 문제에 대한 새로운 솔루션이 있습니다. java.time 패키지 에서 자바 (8)는 추상 클래스 포함 java.time.Clock명시 적 목적을 :

필요에 따라 대체 클럭을 연결할 수 있도록

의 구현을 꽂을 수 Clock있지만 필요에 맞게 이미 구현 된 것을 찾을 수도 있습니다. 편의상 java.time에는 특수 구현을 생성하는 정적 메소드가 포함되어 있습니다. 이러한 대체 구현은 테스트 중에 유용 할 수 있습니다.

변경된 케이던스

다양한 tick…방법은 다른 케이던스로 현재 순간을 증가시키는 클럭을 생성합니다.

기본값 은 하드웨어에 따라 Java 8 및 Java 9에서 밀리 초 단위나노초 단위Clock업데이트 된 시간을보고합니다 . 실제 현재 모멘트를 다른 세분성으로보고하도록 요청할 수 있습니다.

거짓 시계

일부 클럭은 거짓말로 인해 호스트 OS의 하드웨어 클럭과 다른 결과를 생성 할 수 있습니다.

  • fixed -변하지 않는 (증가하지 않는) 단일 모멘트를 현재 모멘트로보고합니다.
  • offset-현재 순간을보고하지만 전달 된 Duration인수 로 이동합니다 .

예를 들어, 올해 초 크리스마스 첫 순간에 잠그십시오. 다시 말해서 산타와 그의 순록이 처음으로 멈출 때 . 요즘 가장 빠른 시간대 Pacific/Kiritimati+14:00입니다.

LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );

항상 같은 순간을 반환하려면 특수 고정 시계를 사용하십시오. 우리는 Kiritimati 에서 크리스마스의 첫 순간을 맞이합니다. UTC 는 12 월 24 일 전일 오전 10 시보 다 일찍 14 시간 일찍 벽시계 시간 을 표시합니다 .

Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );

instant.toString () : 2016-12-24T10 : 00 : 00Z

zdt.toString () : 2016-12-25T00 : 00 + 14 : 00 [태평양 / 키리 마티]

IdeOne.com의 라이브 코드를 참조하십시오 .

실제 시간, 다른 시간대

Clock구현에서 할당 한 시간대를 제어 할 수 있습니다 . 이것은 일부 테스트에 유용 할 수 있습니다. 그러나 프로덕션 코드에서는 항상 선택 사항 ZoneId또는 ZoneOffset인수를 명시 적으로 지정 해야하는 것이 좋습니다 .

UTC를 기본 영역으로 지정할 수 있습니다.

ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );

특정 시간대를 지정할 수 있습니다. 지정 적절한 시간대 이름 의 형식 continent/region예컨대, America/Montreal, Africa/Casablanca, 또는 Pacific/Auckland. 표준 시간대 가 아니EST 거나 표준화되지 않았으며 고유하지 않은 3-4 문자 약어를 사용하지 마십시오 .IST

ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );

JVM의 현재 기본 시간대를 특정 Clock오브젝트 의 기본값으로 지정할 수 있습니다 .

ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );

이 코드를 실행하여 비교하십시오. 그것들은 모두 타임 라인에서 같은 시점, 같은 시점을보고합니다. 그것들은 벽시계 시간 에서만 다릅니다 . 다시 말해, 같은 것을 말하는 세 가지 방법, 같은 순간을 나타내는 세 가지 방법.

System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );

America/Los_Angeles 이 코드를 실행 한 컴퓨터의 JVM 현재 기본 영역입니다.

zdtClockSystemUTC.toString () : 2016-12-31T20 : 52 : 39.688Z

zdtClockSystem.toString () : 2016-12-31T15 : 52 : 39.750-05 : 00 [미국 / 몬트리올]

zdtClockSystemDefaultZone.toString () : 2016-12-31T12 : 52 : 39.762-08 : 00 [미국 / 로스 앤젤레스]

Instant클래스는 정의에 의해 UTC 항상. 따라서이 세 가지 영역 관련 Clock사용법은 동일한 효과를 갖습니다.

Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );

instantClockSystemUTC.toString () : 2016-12-31T20 : 52 : 39.763Z

instantClockSystem.toString () : 2016-12-31T20 : 52 : 39.763Z

instantClockSystemDefaultZone.toString () : 2016-12-31T20 : 52 : 39.763Z

기본 시계

에 기본적으로 사용되는 구현 Instant.now은에서 반환 된 구현 Clock.systemUTC()입니다. 이것은를 지정하지 않을 때 사용되는 구현 Clock입니다. 시험판 Java 9 소스 코드를Instant.now 참조하십시오 .

public static Instant now() {
    return Clock.systemUTC().instant();
}

기본 ClockOffsetDateTime.nowZonedDateTime.now있다 Clock.systemDefaultZone(). 소스 코드를 참조하십시오 .

public static ZonedDateTime now() {
    return now(Clock.systemDefaultZone());
}

기본 구현의 동작은 Java 8과 Java 9 사이에서 변경되었습니다. Java 8 에서 클래스의 나노초 분해능 저장 기능에도 불구하고 현재 순간은 밀리 초 단위 의 해상도로 캡처됩니다 . Java 9는 물론 컴퓨터 하드웨어 시계의 기능에 따라 나노초의 해상도로 현재 순간을 캡처 할 수있는 새로운 구현을 제공합니다.


java.time에 대하여

java.time의 프레임 워크는 나중에 자바 8에 내장되어 있습니다. 이 클래스는 까다로운 기존에 대신 기존 과 같은 날짜 – 시간의 수업을 java.util.Date, Calendar, SimpleDateFormat.

자세한 내용은 Oracle Tutorial을 참조하십시오 . 많은 예제와 설명을 보려면 스택 오버플로를 검색하십시오. 사양은 JSR 310 입니다.

Joda 타임 프로젝트는 지금에 유지 관리 모드 의로 마이그레이션을 조언 java.time의 클래스.

java.time 객체를 데이터베이스와 직접 교환 할 수 있습니다 . JDBC 4.2 이상을 준수 하는 JDBC 드라이버를 사용하십시오 . 문자열이 필요없고 수업이 필요 없습니다 . Hibernate 5 & JPA 2.2는 java.time을 지원 합니다 .java.sql.*

java.time 클래스는 어디서 구할 수 있습니까?


답변

으로는 존 소총 말했다 :

“Joda Time 사용”은 “java.util.Date/Calendar를 사용하여 X를 어떻게 달성합니까?”

그래서 여기에 간다 (당신이 모든 것을 ( new Date()new DateTime().toDate()) 로 대체했다고 가정 )

//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();

인터페이스가있는 라이브러리를 가져 오려면 (아래 Jon의 주석 참조) 표준 인터페이스뿐만 아니라 구현을 제공하는 Prevayler ‘s Clock을 사용할 수 있습니다. 전체 병은 96kB에 불과하므로 은행을 파기해서는 안됩니다 …


답변

일부 DateFactory 패턴을 사용하는 것은 좋지만 제어 할 수없는 라이브러리는 다루지 않습니다 .System.currentTimeMillis에 의존하는 구현으로 유효성 검사 주석 @Past를 상상하십시오 (그렇습니다).

그래서 우리는 jmockit을 사용하여 시스템 시간을 직접 조롱합니다.

import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
    /**
     * Fake current time millis returns value modified by required offset.
     *
     * @return fake "current" millis
     */
    @Mock
    public static long currentTimeMillis() {
        return INIT_MILLIS + offset + millisSinceClassInit();
    }
}

Mockit.setUpMock(SystemMock.class);

원래의 조롱되지 않은 millis 값을 얻을 수 없기 때문에 대신 나노 타이머를 사용합니다. 이는 벽시계와 관련이 없지만 여기에서 상대적 시간은 충분합니다.

// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}

HotSpot을 사용하면 여러 번의 호출 후 시간이 정상으로 돌아온다는 문서화 된 문제가 있습니다. 다음은 문제 보고서입니다. http://code.google.com/p/jmockit/issues/detail?id=43

이를 극복하기 위해이 인수를 사용하여 하나의 특정 HotSpot 최적화 실행 JVM을 설정해야 -XX:-Inline합니다.

이는 프로덕션에는 적합하지 않지만 테스트에는 적합하며 특히 DataFactory가 비즈니스에 적합하지 않고 테스트로 인해 도입 된 경우에는 응용 프로그램에 절대적으로 투명합니다. 내장 된 JVM 옵션을 다른 시간에 실행하는 것이 좋을 것입니다. 너무 나빠서 이와 같은 해킹이 없으면 불가능합니다.

전체 기사는 내 블로그 게시물 ( http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/)에 있습니다.

완벽한 편리한 클래스 SystemTimeShifter가 게시물에 제공됩니다. 테스트에서 클래스를 사용하거나 다른 시간에 응용 프로그램 (또는 전체 응용 프로그램 서버)을 실행하기 위해 실제 메인 클래스보다 첫 번째 메인 클래스로 매우 쉽게 사용할 수 있습니다. 물론 이것은 프로덕션 환경이 아니라 주로 테스트 목적으로 사용됩니다.

2014 년 7 월 편집 : JMockit은 최근에 많이 바뀌었고 이것을 올바르게 사용하려면 JMockit 1.0을 사용해야합니다 (IIRC). 인터페이스가 완전히 다른 최신 버전으로 확실히 업그레이드 할 수 없습니다. 나는 필요한 것들을 인라인하는 것에 대해 생각하고 있었지만, 새로운 프로젝트에서 이것을 필요로하지 않기 때문에 나는 이것을 전혀 개발하지 않고 있습니다.


답변

Powermock훌륭하게 작동합니다. 그냥 조롱하는 데 사용했습니다 System.currentTimeMillis().


답변

Aspect-Oriented Programming (예 : AspectJ)을 사용하여 System 클래스를 짜서 테스트 케이스 내에서 설정할 수있는 사전 정의 된 값을 리턴하십시오.

또는 응용 프로그램 클래스를 짜서 호출을 System.currentTimeMillis()또는로 리디렉션new Date() 자신의 또 다른 유틸리티 클래스.

직조 시스템 클래스 (java.lang.*그러나 )는 조금 더 까다롭기 때문에 rt.jar에 대해 오프라인 직조를 수행하고 테스트에 별도의 JDK / rt.jar을 사용해야 할 수도 있습니다.

이진 직조 라고 하며 시스템 클래스 직조를 수행하고 그 문제를 피하기위한 특수 도구 도 있습니다 (예 : VM 부트 스트래핑이 작동하지 않을 수 있음)


답변

실제로 VM에서 직접이 작업을 수행 할 수있는 방법은 없지만 테스트 머신에서 프로그래밍 방식으로 시스템 시간을 설정할 수 있습니다. 대부분의 (모든?) OS에는이를위한 명령 줄 명령이 있습니다.