Отмяна на системното време за тестване в Java

1. Общ преглед

В този бърз урок ще се съсредоточим върху различни начини за замяна на системното време за тестване .

Понякога има логика около текущата дата в нашия код. Може би някои извиквания на функции като new Date () или Calendar.getInstance () , които в крайна сметка ще извикат System.CurrentTimeMillis .

За въведение в използването на Java Clock , моля, вижте тази статия тук. Или, за използването на AspectJ, тук.

2. Използване на Clock в java.time

Пакетът java.time в Java 8 включва абстрактен клас java.time.Clock с цел да позволи включване на алтернативни часовници, когато и когато е необходимо. С това можем да включим нашето собствено изпълнение или да намерим такова, което вече е направено, за да задоволи нашите нужди.

За да постигнем нашите цели, горната библиотека включва статични методи за получаване на специални реализации . Ще използваме две от тях, които връщат неизменяема, безопасна за нишки и сериализуема реализация.

Първият е фиксиран . От него можем да получим Clock, който винаги връща един и същ Instant , като гарантира, че тестовете не зависят от текущия часовник.

За да го използваме, се нуждаем от Instant и ZoneOffset :

Instant.now(Clock.fixed( Instant.parse("2018-08-22T10:00:00Z"), ZoneOffset.UTC))

Вторият статичен метод се компенсира . В този часовник обгръща друг часовник, което го прави върнатия обект, способен да получи моменти, които са по-късни или по-ранни от определената продължителност.

С други думи, възможно е да се симулира бягане в бъдеще, в миналото или в произволен момент от времето :

Clock constantClock = Clock.fixed(ofEpochMilli(0), ZoneId.systemDefault()); // go to the future: Clock clock = Clock.offset(constantClock, Duration.ofSeconds(10)); // rewind back with a negative value: clock = Clock.offset(constantClock, Duration.ofSeconds(-5)); // the 0 duration returns to the same clock: clock = Clock.offset(constClock, Duration.ZERO);

С класа Duration е възможно да се манипулира от наносекунди до дни. Също така можем да отречем продължителност, което означава да получим копие от тази продължителност с отрицателна дължина.

3. Използване на програмно-ориентирано програмиране

Друг начин за отмяна на системното време е чрез AOP. С този подход ние можем да изтъкаме клас System, за да върнем предварително дефинирана стойност, която можем да зададем в нашите тестови случаи .

Също така е възможно да преплетете класовете на приложения, за да пренасочите повикването към System.currentTimeMillis () или към new Date () към друг собствен клас на полезност.

Един от начините да се приложи това е използването на AspectJ:

public aspect ChangeCallsToCurrentTimeInMillisMethod { long around(): call(public static native long java.lang.System.currentTimeMillis()) && within(user.code.base.pckg.*) { return 0; } } 

В горния пример улавяме всяко повикване към System.currentTimeMillis () вътре в определен пакет , който в този случай е user.code.base.pckg. * , И връщаме нула всеки път, когато се случи това събитие .

На това място можем да декларираме собствената си реализация, за да получим желаното време в милисекунди.

Едно от предимствата на използването на AspectJ е, че той работи директно на ниво байт код, така че не се нуждае от оригиналния изходен код, за да работи.

Поради тази причина не би трябвало да го компилираме.

4. подигравателен на Instant.now () Метод

Можем да използваме моменталния клас, за да представим моментална точка на времевата линия. Обикновено можем да го използваме за записване на времеви отпечатъци в нашето приложение. Методът now () от този клас ни позволява да получим текущия момент от системния часовник в часовата зона UTC.

Нека да видим някои алтернативи за промяна на поведението му, когато тестваме.

4.1. Претоварване сега () с часовник

Можем да претоварим метода now () с фиксиран екземпляр на Clock . Много от класовете в пакета java.time имат метод now () , който приема параметър Clock , което прави този наш предпочитан подход:

@Test public void givenFixedClock_whenNow_thenGetFixedInstant() { String instantExpected = "2014-12-22T10:15:30Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC")); Instant instant = Instant.now(clock); assertThat(instant.toString()).isEqualTo(instantExpected); }

4.2. Използване на PowerMock

Освен това, ако трябва да модифицираме поведението на метода now () , без да изпращаме параметри, можем да използваме PowerMock :

@RunWith(PowerMockRunner.class) @PrepareForTest({ Instant.class }) public class InstantUnitTest { @Test public void givenInstantMock_whenNow_thenGetFixedInstant() { String instantExpected = "2014-12-22T10:15:30Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC")); Instant instant = Instant.now(clock); mockStatic(Instant.class); when(Instant.now()).thenReturn(instant); Instant now = Instant.now(); assertThat(now.toString()).isEqualTo(instantExpected); } }

4.3. Използване на JMockit

Като алтернатива можем да използваме библиотеката JMockit .

JMockit ни предлага два начина за подигравка със статичен метод. Единият използва клас MockUp :

@Test public void givenInstantWithJMock_whenNow_thenGetFixedInstant() { String instantExpected = "2014-12-21T10:15:30Z"; Clock clock = Clock.fixed(Instant.parse(instantExpected), ZoneId.of("UTC")); new MockUp() { @Mock public Instant now() { return Instant.now(clock); } }; Instant now = Instant.now(); assertThat(now.toString()).isEqualTo(instantExpected); }

А другата използва класа Очаквания :

@Test public void givenInstantWithExpectations_whenNow_thenGetFixedInstant() { Clock clock = Clock.fixed(Instant.parse("2014-12-23T10:15:30.00Z"), ZoneId.of("UTC")); Instant instantExpected = Instant.now(clock); new Expectations(Instant.class) { { Instant.now(); result = instantExpected; } }; Instant now = Instant.now(); assertThat(now).isEqualTo(instantExpected); }

5. подигравателен на LocalDateTime.now () Метод

Друг полезен клас в пакета java.time е класът LocalDateTime . Този клас представлява дата-час без часова зона в календарната система ISO-8601. Методът now () от този клас ни позволява да получим текущата дата-час от системния часовник в часовата зона по подразбиране.

Можем да използваме същите алтернативи, за да се подиграваме, както видяхме преди. Например претоварване сега () с фиксиран часовник :

@Test public void givenFixedClock_whenNow_thenGetFixedLocalDateTime() { Clock clock = Clock.fixed(Instant.parse("2014-12-22T10:15:30.00Z"), ZoneId.of("UTC")); String dateTimeExpected = "2014-12-22T10:15:30"; LocalDateTime dateTime = LocalDateTime.now(clock); assertThat(dateTime).isEqualTo(dateTimeExpected); }

6. Заключение

В тази статия разгледахме различни начини за замяна на системното време за тестване. Първо разгледахме родния пакет java.time и неговия клас Clock . След това видяхме как да приложим аспект, за да изтъкаме клас System . И накрая, видяхме различни алтернативи за подиграване на метода now () в класовете Instant и LocalDateTime .

Както винаги, примерни кодове могат да бъдат намерени в GitHub.