Подтвердить что ты не робот

Mockito: Как насмехаться над интерфейсом JodaTime

Я использую JodaTime#DateTime, и мне нужно высмеять его поведение. Так как невозможно напрямую макет JodaTime#DateTime, я создаю его интерфейс

Clock.java

public interface Clock {
    DateTime getCurrentDateTimeEST();
    DateTime getFourPM_EST();
    DateTime getSevenPM_EST();
}

JodaTime.java

public class JodaTime implements Clock {

    @Override
    public DateTime getCurrentDateTimeEST() {
        return new DateTime(DateTimeZone.forID("EST"));
    }

    @Override
    public DateTime getFourPM_EST() {
        DateTime current = getCurrentDateTimeEST();
        return new DateTime(current.getYear(), current.getMonthOfYear(), 
                current.getDayOfMonth(), 16, 0, 0, 0, DateTimeZone.forID("EST"));
    }

    @Override
    public DateTime getSevenPM_EST() {
        DateTime current = getCurrentDateTimeEST();
        return new DateTime(current.getYear(), current.getMonthOfYear(), 
                current.getDayOfMonth(), 19, 0, 0, 0, DateTimeZone.forID("EST")); 
    }   
}

Вот метод, который я хочу проверить

public class PrintProcessor{

  Clock jodaTime;

  public PrintProcessor(){
      jodaTime = new JodaTime();
  }
  ...
  public String getPrintJobName(Shipper shipper){
    String printJobName = null;
    //Get current EST time
    if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) ||
            jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST())){   //Before 4PM EST and after 7PM EST
        switch(shipper){
        case X:
        ...
    }else if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getSevenPM_EST())){ //Between 4PM-7PM EST
        switch(shipper){
        case X:
        ... 
    }
    return printJobName;
  }
}

Как вы можете видеть, printJobName зависит от текущего времени дня относительно временного интервала [4 PM-7PM] EST и имени грузоотправителя. Поскольку Shipper будет проходить через параметр, мы можем unit test это не проблема. Но мне нужно высмеять время. Итак, вот что я пытаюсь

@Test
public void testGetPrintJobNameBeforeFourPM(){
    DateTime current = new DateTime(DateTimeZone.forID("EST"));
    Clock clock = mock(Clock.class);
    //Always return 6pm when I try to ask for the current time
    when(clock.getCurrentDateTimeEST()).thenReturn(new DateTime(current.getYear(), current.getMonthOfYear(), 
            current.getDayOfMonth(), 18, 0, 0, 0, DateTimeZone.forID("EST")));
    //Test for Fedex
    String printJobName = printProcessor.getPrintJobName(Shipper.X);
    assertEquals("XNCRMNCF", printJobName);
}

Тест должен завершиться с ошибкой, так как я прохожу через 6 вечера, но XNCRMNCF - это имя до 4 вечера. Мне также нужно высмеять printProcessor. Если я ошибаюсь. Как его исправить? Я пытаюсь научиться писать высокоуровневый Java-код, пожалуйста, очень критикуйте мой код. Я действительно хочу узнать

4b9b3361

Ответ 1

Это классический случай тестирования, показывающий потенциальный недостаток дизайна. Вы не можете высмеивать JodaTime, потому что у вас есть жесткая зависимость от этих классов в вашем классе под тестом.

Посмотрите на SOLID принципы, чтобы понять, почему это может быть проблемой (особенно в этом случае Принцип инверсии зависимостей). Если вы введете JodaTime где-то в качестве зависимости, то в вашем unit test вы сможете заменить реальное значение этого mock, stub или spy в зависимости от ситуации.

Тем не менее: JodaTime - это то, что вряд ли будет введено в чем-либо еще в производственной среде, независимо от того, как долго он живет. Вместо этого в этом случае вам, скорее всего, будет лучше служить составной шаблон дизайна метода. Здесь вы можете извлечь какой-либо расчет/алгоритм, который вы используете для генерации printjobName для другого метода (я не вижу, как вы это делаете здесь, потому что ваш фрагмент кода никогда не присваивает значение этой переменной). Затем вы можете шпионить (частичный макет) вашего тестируемого класса, чтобы только издеваться над этим методом и возвращать фиксированное значение, независимо от реального времени даты доставки JodaTime, например:

public class PrintProcessor {
    ...
    public String getPrintJobName(Shipper shipper) {
        String printJobName = null;
        String timeHash = this.getTimeHash();
        if (this.isBeforeFourPM()) {
            switch(shipper) {
                printJobName = // Do something with timeHash to generate name
            }
        } else {
            ...
        }
        return printJobName;
    }

    public boolean isBeforeFourPM() {
        return (jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) ||
            jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST()));
    }

    public String getTimeHash() {
        ... // Do something to hash the time value in to a String
    }
}

Теперь вы можете написать в своем тесте:

@Test
public void testGetPrintJobNameBeforeFourPM() {
    PrintProcessor concretePrintProcessor = new PrintProcessor();
    PrintProcessor printProcessor = spy(concretePrintProcessor);
    doReturn(true).when(printProcessor).isBeforeFourPM();

    String printJobName = printProcessor.getPrintJobName(Shipper.X);

    assertEquals("XNCRMNCF", printJobName);
}

Ответ 2

Вы никогда не отдаете PrintProcessor свой макет. Выполнение макета объекта - это не то же самое, что давать ваш макет объекту. Итак, когда вы вызываете методы на PrintProcessor, он работает на реальном экземпляре JodaTime. Есть несколько способов дать PrintProcessor ваш макет:

  • Используйте PowerMockito (убедитесь, что вы используете банку PowerMock-mockito, а не банку PowerMock-easymock) и издеваетесь над конструктором JodaTime, чтобы вернуть ваш mocked Clock object whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime); Это вставляет ваш макет везде, где используется конструктор no-arg для JodaTime. Примечание. Это потребует использования макета класса JodaTime.
  • Добавить метод setter для поля Clock jodaTime (который, если определен только в конструкторе, вероятно, должен быть final).
  • Используйте Factory для вашего класса Clock и просто возвращайте макет во время тестов (вы можете использовать PowerMockito для моделирования статических методов).
  • Создайте конструктор с параметром Clock и передайте ваш макет.

Ответ 3

Я думаю, что ты определенно на правильном пути. Создание интерфейса Clock для издевательств - это, безусловно, хорошая идея.

Одна вещь, которую я не вижу в вашем коде: вводя насмешливые часы в printProcessor. После создания макета, я думаю, вам нужно что-то вроде:

printProcessor.setClock(clock)

(это происходит до того, как вы назовете getPrintJobName. Этот установщик должен установить свойство jodaTime в вашем классе PrintProcessor)

Я привык к EasyMock, поэтому, возможно, я ошибаюсь, но я уверен, что вам также нужно будет установить ожидания для вызовов getFourPM_EST и getSevenPM_EST.