如何编写单元测试以确保我的基于日期/时间的代码适用于所有时区以及有/无 DST?

Posted

技术标签:

【中文标题】如何编写单元测试以确保我的基于日期/时间的代码适用于所有时区以及有/无 DST?【英文标题】:How do I write unit tests to make sure my date/time based code works for all time zones and with/out DST? 【发布时间】:2012-06-06 11:36:38 【问题描述】:

我正在使用 JodaTime 2.1,我正在寻找一种模式来对代码进行单元测试,该代码执行日期/时间操作,以确保它在所有时区都表现良好并且独立于 DST。

具体来说:

    如何模拟系统时钟(这样我就不必模拟我调用new DateTime() 的所有地方来获取当前时间) 如何对默认时区执行相同操作?

【问题讨论】:

Make unit tests with dates pass in all time zones and with/out DST的可能重复 不是重复的;这是后续行动。 【参考方案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();

    ....

这将在执行SomeTest 中的每个测试之前将当前时区更改为UTC,并在每次测试后恢复默认时区。

如果要检查多个时区,请使用如下规则:

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 );

这将使用 UTC 在AbstractTZTest 中执行所有测试。对于您要测试的每个时区,您都需要另一个类:

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

由于测试用例是继承的,仅此而已 - 您只需要定义规则即可。

以类似的方式,您可以移动系统时钟。使用调用DateTimeUtils.setCurrentMillisProvider(...)的规则模拟测试在某个时间运行,使用DateTimeUtils.setCurrentMillisSystem()恢复默认值。

注意:您的提供商将需要一种方法来使时钟滴答作响,否则所有新的 DateTime 实例将具有相同的值。每次调用getMillis() 时,我经常将值提前一毫秒。

注意 2:这仅适用于 joda-time。它不影响new java.util.Date()

注意 3:您不能再并行运行这些测试。它们必须按顺序运行,否则其中一个很可能会在另一个测试运行时恢复默认时区。

【讨论】:

不错的解决方案,但我认为如果您并行运行测试,将原始值设置回来可能会有风险,因为第一个测试完成后,会为所有人设置时区(静态字段)其他测试。 @CSpille 正确。如果要并行运行测试,则必须以某种方式将需要此规则的测试分组并围绕组执行规则。【参考方案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()

//...

【讨论】:

我不确定这是否会独立于夏令时。例如,现在我正在获取“美国/太平洋”的时区。现在,因为我们仍在 DST 中,我得到了 -25200 的偏移量。但是当我们在 11 月切换回标准时间时,相同的代码将返回 -28800。我错了吗?

以上是关于如何编写单元测试以确保我的基于日期/时间的代码适用于所有时区以及有/无 DST?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过单元测试最好地监控性能?

案例 | 如何以Sonar为例创建一个适用与所有企业的测试步骤

我如何对这样的代码进行单元测试?

在表格视图中重新排序按日期排序的单元格项目

如何使用 FBTestSession

如何测试断言?