Mockito: Как издеваться над интерфейсом JodaTime - PullRequest
11 голосов
/ 18 мая 2011

Я использую 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 будет передан через параметр, мы можем без проблем проверить его.Но мне нужно высмеивать время.Итак, вот что я пытаюсь

@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-код высокого уровня, пожалуйста, очень критикуйте мой код.Я очень хочу выучить

Ответы [ 3 ]

5 голосов
/ 19 мая 2011

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

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

Однако: 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);
}
3 голосов
/ 19 мая 2011

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

  1. Используйте PowerMockito (убедитесь, что вы используете PowerMock-mockito jar, а не PowerMock-easymock jar) и смоделируйте конструктор JodaTime, чтобы вернуть ваш смоделированный Clock объект whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime); Это будет вставлять ваш макет везде, где используется конструктор без аргументов для JodaTime. Примечание. Для этого потребуется использовать макет класса JodaTime.
  2. Добавить метод установки для поля Clock jodaTime (которое, если определено только в конструкторе, вероятно, должно быть final).
  3. Используйте Factory для вашего Clock класса и просто возвращайте макет во время тестов (вы можете использовать PowerMockito для макетирования статических методов).
  4. Создайте конструктор с параметром Clock и передайте макет.
1 голос
/ 19 мая 2011

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

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

printProcessor.setClock(clock)

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

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

...