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


129

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

작은 배경 :

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

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

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

그래서 내 질문은 :

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

미리 감사드립니다!


1
나는 더 이상 관련이 있는지 모르겠지만, 나의 대답을 참조 AspectJ를 함께 이것을 달성하는 또 다른 방법이있다 : stackoverflow.com/questions/18239859/...
도르 엘로드 Fekete

@ NándorElődFekete 링크의 솔루션은 흥미 롭지 만 코드를 다시 컴파일해야합니다. 레거시 코드를 다루고 있다고 주장하는 사실을 감안할 때 원래 포스터에 다시 컴파일 할 수 있는지 궁금합니다.
cleberz

1
@cleberz AspectJ의 좋은 속성 중 하나는 바이트 코드에서 직접 작동한다는 것입니다. 따라서 원본 소스 코드가 필요하지 않습니다.
Nándor Előd Fekete

@ NándorElődFekete는 힌트를 주셔서 대단히 감사합니다. aspectJ 바이트 코드 레벨 계측 (특히 JDK 클래스 계측)을 알지 못했습니다. 시간이 걸렸지 만 rt.jar의 컴파일 타임 직조와 비 필요 클래스의로드 타임 직조 모두 내 요구에 부응해야한다는 것을 알 수있었습니다 (시스템 재정의). currentTimeMillis () 및 System.nanoTime ()).
cleberz

@cleberz JRE 클래스짜는 또 다른 대답 이 있습니다. 체크 아웃도 가능합니다.
Nándor Előd Fekete

답변:


115

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

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

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

(필요에 따라 등)

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


50
테스트 가능성을 높이기 위해 모든 잠재적 API를 추상화하거나 래핑하여 코드를 복잡하게 만드는 것은 IMHO에게는 좋은 생각이 아닙니다. 간단한 검색 및 바꾸기로 리팩토링을 한 번 수행 할 수 있어도 코드를 읽고 이해하고 유지하기가 훨씬 어려워집니다. 여러 모의 프레임 워크가 System.currentTimeMillis의 동작을 수정할 수 있어야합니다. 그렇지 않은 경우 AOP 또는 자체 제작 도구를 사용하는 것이 더 나은 선택입니다.
jarnbjo

21
@jarnbjo : 글쎄, 당신은 물론 당신의 의견에 오신 것을 환영합니다. 그러나 이것은 꽤 잘 확립 된 기술 IMO이며, 나는 그것을 큰 효과로 사용했습니다. 테스트 가능성을 향상시킬뿐만 아니라 시스템 시간에 대한 의존성을 명시 적으로 만들어 코드 개요를 볼 때 유용 할 수 있습니다.
Jon Skeet

4
@ virgo47 : 동의하지 않으면 동의해야한다고 생각합니다. 코드 를 오염시키는 것으로 "종속성을 명시 적으로 명시"하는 것은 보이지 않습니다 . 코드를보다 명확하게 만드는 자연스러운 방법이라고 생각합니다. 또한 불필요하게 정적 호출을 통해 이러한 종속성을 "완전히 허용되는"것으로 숨기는 것도 없습니다.
Jon Skeet

26
업데이트 Java 8에 내장 된 새로운 java.time 패키지 에는 java.time.Clock"필요에 따라 대체 클럭을 연결할 수 있도록"클래스가 포함 되어 있습니다.
Basil Bourque 8

7
이 답변은 DI가 모두 좋다는 것을 의미합니다. 개인적으로, 나는 실제 응용 프로그램을 잘 활용하는 응용 프로그램을 아직 보지 못했습니다. 대신 무의미한 개별 인터페이스, 상태 비 저장 "객체", 데이터 전용 "객체"및 낮은 응집력 클래스가 풍부하게 나타납니다. IMO, 단순성 및 객체 지향 디자인 (진정한 상태 전체 객체 포함)이 더 나은 선택입니다. static메소드 와 관련하여 메소드가 OO가 아닌지 확인하지만 인스턴스가 인스턴스 상태에서 작동하지 않는 상태 비 저장 종속성을 삽입하는 것은 실제로 더 좋지 않습니다. 어쨌든 효과적으로 "정적"동작을 위장하는 멋진 방법 일뿐입니다.
Rogério

78

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 클래스는 어디서 구할 수 있습니까?


이 정답이다
바랄

42

으로는 존 소총 말했다 :

"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에 불과하므로 은행을 파기해서는 안됩니다 ...


13
아니요, 권장하지 않습니다. 교체 가능한 시계를 향해 움직이지 않습니다. 정적을 계속 사용합니다. Joda Time을 사용하는 것은 물론 좋은 생각이지만 여전히 Clock 인터페이스 또는 이와 유사한 것을 사용하고 싶습니다.
Jon Skeet

@ 존 : 사실-테스트는 괜찮지 만 인터페이스를 갖는 것이 좋습니다.
Stephen

5
@ Jon : 이미 JodaTime을 사용하는 시간 종속 코드를 테스트하려는 경우 부드럽고 IMHO 완벽하고 쉬운 방법입니다. JodaTime으로 모든 것이 이미 해결 되었다면 또 다른 추상화 레이어 / 프레임 워크를 도입하여 바퀴를 재발 명하는 것은 갈 길이 아닙니다.
Stefan Haberl

2
@ JonSkeet : 이것은 Mike가 원래 요구 한 것이 아닙니다. 그는 생산 코드에서 완전히 교체 가능한 시계가 아니라 시간 종속 코드를 테스트하는 도구를 원했습니다. 필자는 아키텍처를 가능한 한 복잡하지만 가능한 한 단순하게 유지하는 것을 선호합니다. 여기에 추가 추상화 계층 (인터페이스)을 소개 할 필요는 없습니다.
Stefan Haberl

2
@StefanHaberl : 엄밀히 "필요하지 않은"많은 것들이 있습니다. 그러나 실제로 제가 제안하는 것은 이미 존재하는 의존성을 명시 적이고보다 쉽게 ​​테스트 할 수 있게 만드는 것 입니다. 스태틱으로 시스템 시간을 허비하는 데는 스태틱의 모든 일반적인 문제가 있습니다. 아무런 이유없이 전역 상태 입니다 . Mike는 현재 날짜와 시간을 사용하여 테스트 가능한 코드를 작성하는 방법을 요청했습니다. 나는 여전히 내 제안이 정적을 효과적으로 푸는 것보다 훨씬 깨끗하고 의존성을 명확하게 믿습니다.
Jon Skeet

15

일부 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). 인터페이스가 완전히 다른 최신 버전으로 확실히 업그레이드 할 수 없습니다. 나는 필요한 것들을 인라인하는 것에 대해 생각하고 있었지만, 새로운 프로젝트에서 이것을 필요로하지 않기 때문에 나는 이것을 전혀 개발하지 않고 있습니다.


1
예를 들어 currentTimeMillis 대신 nanoTime을 조롱하려는 경우 java.util.concurrent를 중단하지 않도록 매우 조심해야합니다. 경험상 타사 라이브러리를 테스트에 사용하기 어려운 경우 라이브러리 입력을 조롱해서는 안됩니다. 라이브러리 자체를 조롱해야합니다.
Dan Berindei 2012 년

1
우리는이 기술을 사용하여 다른 것을 조롱하지 않는 테스트에 사용합니다. 그것은 통합 테스트이며 우리는 몇 년 동안 처음으로 어떤 활동이 일어날 때 어떻게 해야하는지 전체 스택을 테스트합니다. 나노 타임에 대한 좋은 점, 운 좋게도 벽시계가 없기 때문에 그것을 조롱 할 필요가 없습니다. 전혀 의미하지 않습니다. Java가 이미 한 번 이상 필요하고 시간이 지남에 따라 기능이 바뀌고 시스템 시간이 혼란 스럽기 때문에 Java를보고 싶습니다.
virgo47

7

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


Powermock을 사용하려고했는데 올바르게 이해하면 실제로 수신자 측을 조롱하지 않을 것입니다. 대신 모든 발신자를 찾고 모의를 적용합니다. 실제로 응용 프로그램이 변경된 시간 내에 작동하는지 확인하려면 클래스 경로의 모든 클래스를 확인해야하므로 시간이 많이 걸릴 수 있습니다. Powermock의 조롱 방법에 대해 틀린 경우 수정하십시오.
virgo47

1
아니요, 잘못 이해했습니다. PowerMock은 "클래스 경로의 모든 클래스를 확인하지"않습니다. @PrepareForTest테스트 클래스 의 주석을 통해 확인하도록 지정한 클래스 만 검사합니다 .
Rogério

1
@ Rogério-정확히 이해하는 방법입니다- "당신은 그것을 내버려 두어야합니다 ..." 즉 System.currentTimeMillis, 클래스 패스 (모든 lib)의 어느 곳으로나 전화를 기대 하면 모든 클래스를 확인해야합니다. 그것이 제가 의미 한 바입니다. 요점은 발신자 측에서 해당 행동을 조롱한다는 것입니다 ( "구체적으로 말하세요"). 간단한 테스트에는 문제가 없지만 어디에서 메소드를 호출하는지 확실하지 않은 테스트에는 적합하지 않습니다 (예 : 라이브러리가 포함 된 더 복잡한 구성 요소 테스트). 그렇다고 Powermock이 전혀 잘못되었다는 의미는 아닙니다. 단지 이러한 유형의 테스트에 사용할 수 없다는 의미입니다.
virgo47'12

1
@ virgo47 네, 무슨 말인지 알겠습니다. PowerMock이 클래스 경로의 모든 클래스를 자동으로 검사해야한다는 것을 의미한다고 생각했습니다. 그러나 실제로 단위 테스트의 경우 시간을 읽는 클래스를 알고 있으므로이를 지정하는 것은 문제가되지 않습니다. 통합 테스트의 경우 실제로 모든 사용 클래스를 지정해야하는 PowerMock의 요구 사항이 문제 일 수 있습니다.
Rogério

6

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

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

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

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


물론 이것은 작동하지만 AOP 도구보다 조롱 도구를 사용하는 것이 훨씬 쉽습니다. 후자의 도구는 문제의 목적에 비해 너무 일반적입니다.
Rogério

3

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


예를 들어, Windows에서 datetime명령. Linux에서 date명령.
Jeremy Raymond

왜 AOP 또는 도구화로 그렇게 할 수 없는가?
jarnbjo

3

Joda Time 및 PowerMock을 사용하지 않고 EasyMock을 사용하는 Java 8 웹 애플리케이션에서 JUnit 테스트 목적으로 현재 시스템 시간을 대체하는 효과적인 방법입니다.

수행해야 할 작업은 다음과 같습니다.

시험을 거친 수업에서해야 할 일

1 단계

java.time.Clock테스트 된 클래스에 새 속성을 추가하고 MyService인스턴스화 블록 또는 생성자를 사용하여 새 속성이 기본값에서 올바르게 초기화되는지 확인하십시오.

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { 
    initDefaultClock(); // initialisation in an instantiation block, but 
                        // it can be done in a constructor just as well
  }
  // (...)
}

2 단계

clock현재 날짜-시간을 호출하는 메소드에 새 속성 을 삽입하십시오 . 예를 들어, 내 경우에는 내가 dataase에 저장된 날짜 전에 무슨 일이 있었 여부의 검사를 수행했다 LocalDateTime.now()내가 함께 remplaced하는, LocalDateTime.now(clock)그래서 같이 :

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}

시험 수업에서해야 할 일

3 단계

테스트 클래스에서 mock clock 객체를 생성하고 테스트 된 메소드를 호출하기 직전에 테스트 된 클래스의 인스턴스에 주입 doExecute()한 다음 바로 다음과 같이 재설정하십시오.

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;
  private int month = 2;
  private int day = 3;

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot

    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method

    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }

디버그 모드에서 확인하면 2017 년 2 월 3 일이 myService인스턴스 에 올바르게 삽입되어 비교 명령에 사용 된 후 를 사용하여 현재 날짜로 올바르게 재설정되었습니다 initDefaultClock().


2

내 의견으로는 비 침습적 솔루션 만 작동 할 수 있습니다. 특히 외부 라이브러리와 큰 레거시 코드 기반이있는 경우 시간을 조롱 할 수있는 확실한 방법이 없습니다.

JMockit ... 제한된 수의 경우에만 작동 시간

PowerMock & Co는 클라이언트 를 System.currentTimeMillis () 로 조롱해야합니다 . 다시 침습적 옵션.

이것으로부터 나는 언급 된 javaagent 또는 aop 접근 방식이 전체 시스템에 투명하다는 것을 알 수 있습니다. 아무도 그렇게하고 그런 해결책을 가리킬 수 있습니까?

@ jarnbjo : javaagent 코드를 보여 주시겠습니까?


3
약간의 -XX : -Inline hack (Sun 's HotSpot에서)으로 무제한으로 작동하는 jmockit 솔루션 : virgo47.wordpress.com/2012/06/22/changing-system-time-in-java 완전한 편리한 클래스 SystemTimeShifter가 제공됩니다. 게시물에. 클래스는 테스트에서 사용하거나 다른 시간에 응용 프로그램 (또는 전체 응용 프로그램 서버)을 실행하기 위해 실제 메인 클래스보다 첫 번째 메인 클래스로 매우 쉽게 사용할 수 있습니다. 물론 이것은 프로덕션 환경이 아니라 주로 테스트 목적으로 사용됩니다.
virgo47

2

Linux를 실행중인 경우 libfaketime의 마스터 브랜치를 사용하거나 커밋 4ce2835 를 테스트 할 .

Java 애플리케이션을 조롱하려는 시간으로 환경 변수를 설정하고 ld-preloading을 사용하여 실행하십시오.

# bash
export FAKETIME="1985-10-26 01:21:00"
export DONT_FAKE_MONOTONIC=1
LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 java -jar myapp.jar

두 번째 환경 변수는 Java 응용 프로그램에서 가장 중요하며 그렇지 않은 경우 정지됩니다. 작성시 libfaketime의 마스터 브랜치가 필요합니다.

체계적으로 관리되는 서비스의 시간을 변경하려면 단위 파일 재정의에 다음을 추가하십시오 (예 : elasticsearch의 경우) /etc/systemd/system/elasticsearch.service.d/override.conf.

[Service]
Environment="FAKETIME=2017-10-31 23:00:00"
Environment="DONT_FAKE_MONOTONIC=1"
Environment="LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1"

`systemctl daemon-reload를 사용하여 systemd를 다시로드하는 것을 잊지 마십시오


-1

System.currentTimeMillis()인수가 있는 메소드를 조롱하려는 경우 anyLong()Matchers 클래스를 인수로 전달할 수 있습니다 .

추신 : 위의 트릭을 사용하여 테스트 사례를 성공적으로 실행할 수 있으며 PowerMock 및 Mockito 프레임 워크를 사용하는 테스트에 대한 자세한 내용을 공유하기 만합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.