Java System.currentTimeMillis를 테스트 시간 민감 코드에 대해 재정의
코드 또는 JVM 인수를 사용하여 현재 시간을 재정의할 수 있는 방법이 있는가?System.currentTimeMillis
, 호스트 시스템에서 시스템 시계를 수동으로 변경하는 것 외에?
약간의 배경:
당사는 현재(즉, 월 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
코드 또는 JVM 인수를 사용하여 호스트 시스템에서 시스템 시계를 수동으로 변경하는 것 외에 System.currentTimeMillis를 통해 표시되는 현재 시간을 재정의할 수 있는 방법이 있는가?
네
Instant.now(
Clock.fixed(
Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
)
)
Clock
.timeo에서
우리는 가짜 날짜 시간 값으로 테스트를 용이하게 하기 위해 플러그형 클럭 교체 문제에 대한 새로운 해결책을 가지고 있다.Java 8의 Java.time 패키지는 추상 클래스를 포함하며, 다음과 같은 명확한 목적을 가지고 있다.
필요할 때 다른 시계의 플러그를 꽂을 수 있도록 하다
의 구을을 수 있다.Clock
비록 당신은 이미 당신의 필요를 충족시키기 위해 만들어진 것을 찾을 수 있다.편의를 위해 Java.time은 특별한 구현을 위한 정적 방법을 포함한다.이러한 대체 구현은 테스트 중에 유용할 수 있다.
변형된 캐던스
여러 가지tick…
메소드는 현재 모멘트를 증가시키는 시계를 다른 잡음으로 생산한다.
기Clock
하드웨어에 따라 자주 업데이트되는 시간을 Java 8 및 Java 9에서 나노초 단위로 보고한다.당신은 다른 세분성으로 보고되도록 진정한 현재 순간을 요청할 수 있다.
tickSeconds
- 전체 초 단위로 증가tickMinutes
- 전체 분 단위의 증가tick
- 전달된 인수에 따라 증가함.
거짓시계
어떤 시계는 거짓말을 하여 호스트 OS의 하드웨어 시계와는 다른 결과를 만들어 낼 수 있다.
예를 들어, 올해 가장 이른 크리스마스의 첫 순간을 잠가라.다시 말해서, 산타와 그의 순록이 그들의 첫 번째 정거장을 했을 때.요즘 가장 빠른 시간대는+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 );
항상 같은 순간을 되돌리려면 그 특별한 고정시계를 사용해라.우리는 키리티마티에서 크리스마스의 첫 순간을 맞이하게 되는데, 12월 24일 이전날 오전 10시에 UTC가 벽시계를 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
, , , 또는 같은Pacific/Auckland
. 다음과 같은 3-4 문자 약어를 사용하지 마십시오.EST
또는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 현재 기본 영역이었습니다.
zdtClock시스템UTC.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[아메리카/로스_엔젤레스]
클래스는 항상 정의상 UTC에 있다.그래서 이 세 구역은Clock
사용법은 정확히 같은 효과를 가진다.
Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );
인스턴트클락시스템UTC.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
에 대한 사전 릴리스 Java 9 소스 코드를 참조하십시오.
public static Instant now() {
return Clock.systemUTC().instant();
}
기Clock
을 위해OffsetDateTime.now
그리고ZonedDateTime.now
이다Clock.systemDefaultZone()
. 소스 코드를 참조하십시오.
public static ZonedDateTime now() {
return now(Clock.systemDefaultZone());
}
기본 구현 동작은 Java 8과 Java 9 사이에서 변경되었다.자바 8에서 현재 모멘트는 클래스의 분해능 저장 능력에도 불구하고 밀리초 단위의 분해능으로 캡처된다.Java 9는 물론 컴퓨터 하드웨어 시계의 기능에 따라 나노초의 분해능으로 현재의 순간을 포착할 수 있는 새로운 구현을 제공한다.
java.time 정보
자바.시간 프레임워크는 Java 8 이상에 내장되어 있다.이러한 수업은 , , & 같은 골치 아픈 오래된 레거시 날짜-시간 수업을 대체한다.
자세한 내용은 Oracle 자습서를 참조하십시오.그리고 스택 오버플로를 검색하여 많은 예와 설명을 참조하십시오.사양은 JSR 310이다.
현재 유지보수 모드에 있는 조다 타임 프로젝트는 Java.time 클래스로의 마이그레이션을 권고한다.
Java.time 객체를 데이터베이스와 직접 교환할 수 있다.JDBC 4.2 이상과 호환되는 JDBC 드라이버를 사용하십시오.끈이 필요 없고, 끈이 필요 없다.java.sql.*
반최대 절전 모드 5 & JPA 2.2는 Java.time을 지원한다.
Java.time 클래스를 가져올 위치
- 자바 SE 8, 자바 SE 9, 자바 SE 10, 자바 SE 11 및 이후 버전 - 번들 구현이 포함된 표준 Java API의 일부.
- 자바 9는 몇 가지 사소한 특징과 수정 사항을 가지고 왔다.
- Java SE 6 및 자바 SE 7
- 대부분의 Java.time 기능은 스리텐 백포트에서 Java 6 & 7로 백포트된다.
- 안드로이드
존 스키트의 말처럼:
"Joda Time 사용"은 "Java.util로 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();
인터페이스가 있는 라이브러리를 가져오려면(아래 John의 설명 참조), 표준 인터페이스뿐만 아니라 구현을 제공하는 Prevayler 시계를 사용하십시오.가득 찬 항아리는 96kB밖에 안 되니까 둑을 무너뜨리면 안 되는데...
일부 DateFactory 패턴을 사용하면 제어할 수 없는 라이브러리를 포함하지는 않지만 System.currentTimeMillis(시스템.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을 사용해야 한다(IERC).인터페이스가 완전히 다른 최신 버전으로 업그레이드할 수 없음.나는 필요한 것만 줄여서 쓸까 생각 중이었는데, 새로운 프로젝트에서 이것이 필요 없기 때문에 나는 이것을 전혀 개발하지 않을 것이다.
파워매크는 아주 잘 작동한다.그냥 조롱하는 데 썼을 뿐이야.System.currentTimeMillis()
.
테스트 사례에서 설정할 수 있는 미리 정의된 값을 반환하려면 AOP(예: SensionJ)를 사용하여 시스템 클래스를 엮으십시오.
또는 응용 프로그램 클래스를 짜서 통화를 다음으로 리디렉션System.currentTimeMillis()
또는 ~로new Date()
다른 유틸리티 클래스로 이동하십시오.
직조 시스템 클래스().java.lang.*
그러나 )은 조금 더 까다롭기 때문에 rt.jar의 경우 오프라인 직조를 수행해야 하며 테스트에는 별도의 JDK/rt.jar를 사용해야 할 수 있다.
Binary Wabiling이라고 하며 시스템 클래스의 Wabiling을 수행하고 이에 대한 일부 문제를 방지하기 위한 특수 툴(예: VM 부트스트래핑이 작동하지 않을 수 있음)
EasyMock, Joda Time 및 PowerMock이 없는 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
현재 날짜/시간을 요구하는 방법으로.예를 들어, 나의 경우 데이터아세스에 저장된 날짜가 이전에 발생했는지 여부를 확인해야 했다.LocalDateTime.now()
, 내가 바꿔치기한 것.LocalDateTime.now(clock)
like so, like so:
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단계
테스트 클래스에서 모의 클럭 객체를 만들어 테스트된 메소드를 호출하기 직전에 테스트된 클래스의 인스턴스에 주입하십시오.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
인스턴스(instance)를 사용하여 비교 지침에 사용된 다음 다음 다음을 사용하여 현재 날짜로 적절하게 재설정됨initDefaultClock()
.
내 생각에는 비침습적 해결책만이 효과가 있을 것 같다.특히 외부 libs와 큰 레거시 코드 기반을 가지고 있다면 시간을 조롱할 수 있는 믿을 만한 방법이 없다.
JMockit... 제한된 횟수에만 사용 가능
PowerMock & Co...시스템.currentTimeMillis()로 클라이언트를 조롱할 필요가 있다.다시 한번 침습적인 선택이다.
이것으로부터 나는 단지 언급된 자바에이전트나 op 접근방식이 전체 시스템에 투명하다는 것을 볼 뿐이다.누가 그런 짓을 했고 그런 해결책을 지적할 수 있는 사람이 있는가?
@jarnbjo: 자바에이전트 코드 일부를 보여주시겠습니까?
여기 PowerMockito를 사용한 예가 있다.또한 새로운 날짜()의 예가 있다.
시스템 클래스를 조롱하는 방법에 대한 자세한 정보.
@RunWith(PowerMockRunner.class)
@PrepareForTest(LegacyClass.class)
public class SystemTimeTest {
private final Date fakeNow = Date.from(Instant.parse("2010-12-03T10:15:30.00Z"));
@Before
public void init() {
PowerMockito.mockStatic(System.class);
PowerMockito.when(System.currentTimeMillis()).thenReturn(fakeNow.getTime());
System.out.println("Fake currentTimeMillis: " + System.currentTimeMillis());
}
@Test
public void legacyClass() {
new LegacyClass().methodWithCurrentTimeMillis();
}
}
테스트 중인 일부 레거시 클래스:
class LegacyClass {
public void methodWithCurrentTimeMillis() {
long now = System.currentTimeMillis();
System.out.println("LegacyClass System.currentTimeMillis() is " + now);
}
}
콘솔 출력
Fake currentTimeMillis: 1291371330000
LegacyClass System.currentTimeMillis() is 1291371330000
VM에서 직접 이 작업을 수행할 수 있는 방법은 없지만 테스트 시스템의 시스템 시간을 프로그래밍 방식으로 설정할 수 있는 모든 방법을 사용할 수 있다.대부분 (모두?)OS에는 이를 위한 명령줄 명령이 있다.
Linux를 실행 중인 경우 libfaketime의 마스터 분기를 사용하거나 테스트 시 commit 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
두 번째 환경 변수는 그렇지 않으면 동결될 자바 애플리케이션에서 가장 중요하다.글쓰기에 있어서 libfaketime의 마스터 분기가 필요하다.
파일 를 들어 탄력적인 이 은 장장(長長長)을 사용하여 할 수 있다. 예를 들어 탄력적인 검색을 위해 다음과 같이 하십시오./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 데몬-reload를 사용하여 systemd를 다시 로드하는 것을 잊지 마십시오.
다음 작업을 수행하는 방법을 조롱하려면System.currentTimeMillis()
그러면 당신은 논쟁으로 Matchers 클래스를 통과할 수 있다.
P.S. 위의 트릭을 사용하여 성공적으로 테스트 케이스를 실행할 수 있으며, PowerMock과 Mockito 프레임워크를 사용하고 있다는 테스트에 대한 자세한 정보를 공유할 수 있다.
'IT이야기' 카테고리의 다른 글
모키토로 특정 유형의 목록을 캡처하는 방법 (0) | 2022.05.13 |
---|---|
강력한 비동기 검증자 - 어떻게 철회할 것인가? (0) | 2022.05.13 |
Java에서 long을 int로 변환하려면 어떻게 해야 하는가? (0) | 2022.05.12 |
Vue.js에서 가치를 비반복적으로 만드는 방법은? (0) | 2022.05.12 |
열거형 변수를 문자열로 변환하는 방법 (0) | 2022.05.12 |