Как написать модульные тесты, чтобы убедиться, что мой код, основанный на дате/времени, работает для всех часовых поясов и с/выводом DST?

Я использую JodaTime 2.1, и я ищу шаблон для unit test кода, который выполняет операции даты и времени, чтобы убедиться, что он хорошо себя ведет во всех часовых поясах и не зависит от DST.

В частности:

  • Как я могу высмеять системные часы (поэтому мне не нужно издеваться над всеми местами, где я звоню new DateTime(), чтобы получить текущее время)
  • Как я могу сделать то же самое для часового пояса по умолчанию?

Ответ 1

Вы можете использовать @Rule для этого. Вот код для правила:

import org.joda.time.DateTimeZone;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class UTCRule extends TestWatcher {

    private DateTimeZone origDefault = DateTimeZone.getDefault();

    @Override
    protected void starting( Description description ) {
        DateTimeZone.setDefault( DateTimeZone.UTC );
    }

    @Override
    protected void finished( Description description ) {
        DateTimeZone.setDefault( origDefault );
    }
}

Вы можете использовать правило следующим образом:

public class SomeTest {

    @Rule
    public UTCRule utcRule = new UTCRule();

    ....
}

Это изменит текущий часовой пояс на UTC перед выполнением каждого теста в SomeTest и восстановит часовой пояс по умолчанию после каждого теста.

Если вы хотите проверить несколько часовых поясов, используйте такое правило:

import org.joda.time.DateTimeZone;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;

public class TZRule extends TestWatcher {

    private DateTimeZone origDefault = DateTimeZone.getDefault();

    private DateTimeZone tz;

    public TZRule( DateTimeZone tz ) {
        this.tz = tz;
    }

    @Override
    protected void starting( Description description ) {
        DateTimeZone.setDefault( tz );
    }

    @Override
    protected void finished( Description description ) {
        DateTimeZone.setDefault( origDefault );
    }
}

Поместите все затронутые тесты в абстрактный базовый класс AbstractTZTest и расширьте его:

public class UTCTest extends AbstractTZTest {
    @Rule public TZRule tzRule = new TZRule( DateTimeZone.UTC );
}

Это выполнит все тесты в AbstractTZTest с UTC. Для каждого часового пояса, который вы хотите проверить, вам понадобится другой класс:

public class UTCTest extends AbstractTZTest {
    @Rule public TZRule tzRule = new TZRule( DateTimeZone.forID( "..." );
}

Поскольку тестовые случаи наследуются, это все - вам просто нужно определить правило.

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

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

Примечание 2: Это работает только с Joda-Time. Это не влияет на new java.util.Date().

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

Ответ 2

for (String zoneId : DateTimeZone.getAvailableIDs())
{
   DateTime testedDate1;
   DateTime testedDate2;
   try
   {
      final DateTimeZone tz = DateTimeZone.forID(zoneId);
      // your test with testedDate1 and testedDate2 
   }
   catch (final IllegalArgumentException e)
   {
      // catching DST problem
      testedDate1 = testetDate1.plusHours(1);
      testedDate2 = testetDate2.plusHours(1);
      // repeat your test for this dates
   }
}

Изменить для одиночного теста

DateTimeZone default;  

DateTimeZone testedTZ;

@Before
public void setUp()
{
   default = GateTimeZone.getDefault();
   DateTimeZone.setDefault
}  

@After
public void tearDown()
{
   default = GateTimeZone.setDefault();
   DateTimeZone.setDefault(testedTZ)
}   

@Test
public void test()
{
//...
}