2014-11-21 5 views
36

Java 8 представила java.time.Clock, который может использоваться в качестве аргумента для многих других объектов java.time, что позволяет вам вводить в них реальные или поддельные часы. Например, я знаю, что вы можете создать Clock.fixed(), а затем позвонить Instant.now(clock) и он вернет фиксированный Instant, который вы предоставили. Это звучит идеально для модульного тестирования!Единичное тестирование класса с Java 8 Clock

Однако у меня возникли проблемы с выяснением, как лучше всего использовать это. У меня есть класс, похожий на следующий:

public class MyClass { 
    private Clock clock = Clock.systemUTC(); 

    public void method1() { 
     Instant now = Instant.now(clock); 
     // Do something with 'now' 
    } 
} 

Теперь я хочу проверить этот код. Мне нужно установить clock для создания фиксированных времен, чтобы я мог тестировать method() в разное время. Ясно, что я мог бы использовать отражение, чтобы установить член clock на определенные значения, но было бы неплохо, если бы мне не пришлось прибегать к размышлениям. Я мог бы создать общедоступный метод setClock(), но это не так. Я не хочу добавлять аргумент Clock к методу, потому что реальный код не должен касаться передачи в часах.

Каков наилучший подход для обработки этого? Это новый код, чтобы я мог реорганизовать класс.

Редактировать: Чтобы уточнить, мне нужно иметь возможность построить один объект MyClass, но иметь возможность иметь этот один объект, чтобы увидеть два разных значения часов (как если бы это был обычный системный тактовый сигнал). Таким образом, я не могу передать фиксированные часы в конструктор.

+0

* Теперь я хочу проверить этот код. * Вы должны указать, какое поведение вы ожидаете от 'MyClass'. Это будет информировать о подходе, который следует здесь. – Jubobs

+0

Следующее за этим: Я думаю, что в основном это сводится к тому, что вы не можете использовать 'Clock.fixed' для модульного тестирования так, как я надеялся. Нормальные насмешливые подходы потребуются. – Mike

ответ

28

Позвольте мне ответ Джона Скита и комментарии в коде:

класс под тест:

public class Foo { 
    private final Clock clock; 
    public Foo(Clock clock) { 
     this.clock = clock; 
    } 

    public void someMethod() { 
     Instant now = clock.instant(); // this is changed to make test easier 
     System.out.println(now); // Do something with 'now' 
    } 
} 

модульного тестирования:

public class FooTest() { 

    private Foo foo; 
    private Clock mock; 

    @Before 
    public void setUp() { 
     mock = mock(Clock.class); 
     foo = new Foo(mock); 
    } 

    @Test 
    public void ensureDifferentValuesWhenMockIsCalled() { 
     Instant first = Instant.now();     // e.g. 12:00:00 
     Instant second = first.plusSeconds(1);   // 12:00:01 
     Instant thirdAndAfter = second.plusSeconds(1); // 12:00:02 

     when(mock.instant()).thenReturn(first, second, thirdAndAfter); 

     foo.someMethod(); // string of first 
     foo.someMethod(); // string of second 
     foo.someMethod(); // string of thirdAndAfter 
     foo.someMethod(); // string of thirdAndAfter 
    } 
} 
+1

, возможно, вы захотите изменить 'first',' second' и т. Д. В соответствии с вашими потребностями (например, время должно быть в прошлом), но вы получите эту идею. –

+12

На практике пример теста выше будет работать в NPE, потому что код пользователя, скорее всего, вызовет 'LocalDateTime.now (clock)', а не 'clock.instant()'; 'NullPointerException' будет вызван в тот момент, когда' LocalDateTime' вызывает 'clock.getZone(). getRules()'. Вместо этого должен быть создан объект * real * 'Clock' с вызовом' Clock.fixed (Instant, ZoneId) '. –

+0

@ Rogério моя интерпретация вопроса: «Как мне высмеивать, чтобы возвращать разные значения при последовательных вызовах», таким образом, ответ. Я не знаю достаточно о java 8 времени для 'LocalDateTime.now()' или 'Instant.now()', но если статический метод здесь нужен, его, возможно, нужно будет обернуть в класс, потому что PowerMock/EasyMock [не поддерживает] (http://easymock.org/api/org/easymock/IExpectationSetters.html#andReturn-T-) 'Остановка последовательных вызовов' в [Mockito] (http://mockito.googlecode.com/ svn/tags/1.8.5/javadoc/org/mockito/Mockito.html # 10) –

32

Я не хочу добавлять аргумент Clock к методу, потому что реальный код не должен касаться передачи в часах.

Нет ... но вы можете рассматривать его как конструктор параметра. В основном вы говорите, что вашему классу нужны часы, с которыми нужно работать ... так что это зависимость. Относитесь к нему так же, как и к любой другой зависимости, и вводите его либо в конструктор, либо через метод. (Я лично одобряю конструкторскую инъекцию, но YMMV.)

Как только вы перестанете думать об этом как о чем-то, что вы можете легко построить и подумать об этом как о «просто другой зависимости», тогда вы можете использовать знакомые методы. (Я предполагаю, что вы знакомы с Dependency Injection в целом, по общему признанию.)

+0

Да, я это считал. Однако проблема заключается в том, что передача часов через конструктор позволяет мне установить его на один фиксированный тактовый сигнал. Наверное, я не прояснил ситуацию, но мне нужно настроить часы на несколько значений в рамках одного теста, не создавая новый объект. – Mike

+1

@Mike Пожалуйста, обновите свой образец, чтобы уточнить, что такое ваш вариант использования. – Puce

+0

@Mike Затем либо у вас есть часовое устройство, либо «Clock», либо используйте отражение. Какие другие варианты вы видите на основе ваших требований? –

-1

Вы либо дать MyClass часы, макет Clock, orretrieve часы – есть не много больше возможностей.

Вам не нужно отражать себя, вы используете насмешливую библиотеку.

Мнения различаются в зависимости от того, что такое «правильный» подход.

15

Я немного поздно к игре здесь, но чтобы добавить к другим ответам, предлагающим использовать Clock - это определенно работает, и мы Используя Mockito doAnswer, вы можете создать часы, которые вы можете динамически корректировать по мере прохождения тестов.

Предположим, что этот класс, который был изменен, чтобы принимать часы в конструкторе и ссылаться на часы на звонки Instant.now(clock).

public class TimePrinter() { 
    private final Clock clock; // init in constructor 

    // ... 

    public void printTheTime() { 
     System.out.println(Instant.now(clock)); 
    } 
} 

Затем в вашей тестовой конфигурации:

private Instant currentTime; 
private TimePrinter timePrinter; 

public void setup() { 
    currentTime = Instant.EPOCH; // or Instant.now() or whatever 

    // create a mock clock which returns currentTime 
    final Clock clock = mock(Clock.class); 
    when(clock.instant()).doAnswer((invocation) -> currentTime); 

    timePrinter = new TimePrinter(clock); 
} 

Позже в тесте:

@Test 
public void myTest() { 
    myObjectUnderTest.printTheTime(); // 1970-01-01T00:00:00Z 

    // go forward in time a year 
    currentTime = currentTime.plus(1, ChronoUnit.YEARS); 

    myObjectUnderTest.printTheTime(); // 1971-01-01T00:00:00Z 
} 

Вы хотите сказать, Mockito всегда запускать функцию, которая возвращает текущее значение CURRENTTIME когда вызывается метод instant(). Instant.now(clock) будет звонить clock.instant(). Теперь вы можете перемотки вперед, назад и вообще путешествовать во времени лучше, чем DeLorean.

+0

Этот ответ прекрасен. –

+0

Моим вариантом использования является ускоренная перемотка нескольких секунд в некоторых тестах для проверки некоторых правил проверки на основе времени. Это кажется самым простым и самым элегантным решением для меня. Отлично, спасибо большое. Еще +1 для DeLorean. –

+0

Действительно элегантное решение :) +1 для справки DeLorean. – Bohsen

Смежные вопросы