2012-02-23 2 views
8

У меня есть метод, который реализует различную логику данных, извлеченных из БД, в зависимости от текущей даты.Единичное тестирование временной логики в Java

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

Предложения?

+2

измените свой метод так, чтобы вы действительно дали время в качестве параметра, тогда вы можете проверить его, предоставив любое время, которое вы хотите. Что говорит об этом? – belgther

+0

Можете ли вы рассказать, как вы получаете текущую дату и время? Может быть, насмешка будет работать ... –

+0

На данный момент только с 'new Date()', может быть, мне нужен класс TimeProvider – Alex

ответ

14

Вы можете сгенерировать ожидаемые результаты с использованием текущей даты.

Или вы пишете свою систему, чтобы использовать дату/время, которое вы даете ей при тестировании (а не часы). Таким образом, время всегда соответствует ожидаемому тесту.

я использую что-то вроде

interface TimeSource { 
    long currentTimeMS(); // actually I have currentTimeNS 
    void currentTimeMS(long currentTimeMS); 
} 

enum VanillaTimeSource implements TimeSource { 
    INSTANCE; 

    @Override 
    public long currentTimeMS() { 
     return System.currentTimeMillis(); 
    } 

    @Override 
    public void currentTimeMS(long currentTimeMS) { 
     // ignored 
    } 
} 

class FixedTimeSource implements TimeSource { 
    private long currentTimeMS; 
    @Override 
    public long currentTimeMS() { 
     return currentTimeMS; 
    } 

    @Override 
    public void currentTimeMS(long currentTimeMS) { 
     this.currentTimeMS =    currentTimeMS; 
    } 
} 

В тестах я использую FixedTimeSource, которые могут быть данные, приводимый, например, установленными входами/событиями. В производстве я использую VanillaTimeSource.INSTANCE, который игнорирует время в входах/событиях и использует текущее время.

+0

Первое предложение, вероятно, выиграно Не работай. Я должен был также упомянуть, что данные, полученные в точности, также основаны на дате (в соответствии с определенным столбцом). Это означает, что, хотя логика не отличается, мне пришлось бы генерировать разные результаты, основанные на почти каждый день в году.Я, вероятно, пойду за вторым предложением – Alex

+0

Вы можете сгенерировать ожидаемый результат как часть запуска теста, прежде чем вы проверите результат. Вам не нужно генерировать их заранее. –

+0

Действительно, создание класса/компонента, который получает время в качестве параметра, гарантирует, что он является хорошим компонентом, потому что он отделяется от системного времени. Вы устанавливаете всю конфигурацию приложения («используя системное время») вне компонента. Он должен зависеть только от внешних настраиваемых стимулов. – helios

8

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

Например

public interface TimeProvider { 
    DateTime getCurrentTime(); 
} 

public class UnderTest { 

    // Inject this in some way (e.g. provide it in the constructor) 
    private TimeProvider timeProvider; 

    public void MyMethod() { 
    if (timeProvider.getCurrentTime() == "1234") { 
     // Do something 
    } 
    } 
} 

Теперь в ваших модульных тестов вы можете предоставить поддельную реализацию поставщика времени. В реальном коде производства вы можете просто вернуть текущее время.

+0

Конечно, вы могли бы также предоставить его как параметр, так как 'belgther' предлагает :) –

2

У меня была аналогичная проблема недавно с кодом, я не мог реорганизовать слишком много (временные ограничения, не хотел случайно покончить с чем-либо). У меня был метод, который я хотел проверить, который называется System.currentTimeMillis(), и случай, который я хотел проверить, будет зависеть от того, какое это значение возвращается. Что-то вроде:

public class ClassINeedToTest { 
    public boolean doStuff() { 
     long l = System.currentTimeMillis(); 
     // do some calculation based on l 
     // and return the calculation 
    } 
} 

Чтобы юнит-тестирование, я переработан класс так, чтобы он имел вспомогательный метод, который был защищенный

protected long getCurrentTimeMillis() { 
    // only for unit-testing purposes 
    return System.currentTimeMillis(); 
} 

и этот метод был назван DoStuff(). Это не изменило функциональность, но теперь имел в виду, что, когда я называю его в единичном-тест, я мог бы изменить это, чтобы вернуть определенное значение, как

ClassINeedToTest testClass = new ClassINeedToTest() { 
    protected long getCurrentTimeMillis() { 
     // return specific date for my test 
     return 12456778L; 
    } 
}; 
boolean result = testClass.doStuff(); 
// test result with an assert here 

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

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