比较日期时间的 JUnit 测试方法仅在从套件运行时失败



JUnit 4.11 下运行的单个 JUnit 测试在通过模块测试套件(40 次运行:2 次失败,38 次通过(或类测试套件(40 次运行:6 次失败,34 次通过(运行时大部分时间都失败,但运行测试方法本身不会产生单个失败(50 次运行: 0 次失败,50 次通过(。

总结一下正在发生的事情,如果当前实例与传递给该方法的实例中的org.joda.time.DateTime相同,则equals(Object MyObject)实现返回trueStamp.START或键Stamp.STOP。代码如下:

import org.joda.time.DateTime;
...
private final Map<Stamp, DateTime> timeStampMap;
...
@Override
public boolean equals(Object obj) {
    if (this == obj) { return true; }
    if (obj == null || getClass() != obj.getClass()) { return false; }
    final MyObject other = (MyObject) obj;
    return (Objects.equals(this.timeStampMap.get((Stamp.START)),
                           other.timeStampMap.get(Stamp.START))
            && Objects.equals(this.timeStampMap.get(Stamp.STOP),
                              this.timeStampMap.get(Stamp.STOP)));
}
...
public enum Stamp {
    START,
    STOP
}

以及测试本身:

@Test
@Config(configuration = TestConfig.NO_CONFIG)
public void equalityTest() {
    MyObject a = new MyObject(BigDecimal.TEN);
    MyObject b = a;
    assertThat(a.hashCode(), is(b.hashCode()));
    assertTrue(a.equals(b));
    b = new MyObject(BigDecimal.TEN);
    // This line produces the failure
    assertThat(a, is(not(b)));
}

为什么此测试仅在任一测试套件下运行时会失败,而在单独运行时不会失败?

由于您使用的是 Joda 时间,另一种方法可能是使用 DateTimeUtils.setCurrentMillisFixed(val) 将当前时间固定为您选择的时间。

例如:

@Test
@Config(configuration = TestConfig.NO_CONFIG)
public void equalityTest() {
    DateTimeUtils.setCurrentMillisFixed(someValue);
    MyObject a = new MyObject(BigDecimal.TEN);
    MyObject b = a;
    assertThat(a.hashCode(), is(b.hashCode()));
    assertTrue(a.equals(b));
    DateTimeUtils.setCurrentMillisFixed(someValue + someOffset);
    b = new MyObject(BigDecimal.TEN);
    // This line produces the failure
    assertThat(a, is(not(b)));
}

我建议使代码更具可测试性。您可以传入一个名为 Clock 的接口,而不是让代码直接获取日期:

public interface Clock {
  DateTime now();
}

然后,您可以将Clock添加到构造函数中:

MyObject(BigDecimal bigDecimal, Clock clock) {
  timeStampMap.put(Stamp.START, clock.now());
}

对于生产代码,可以创建一个帮助器构造函数:

MyObject(BigDecimal bigDecimal) {
  this(bigDecimal, new SystemClock());
}

SystemClock看起来像这样的地方:

public class SystemClock implements Clock {
  @Override
  public DateTime now() {
    return new DateTime();
  }
}

您的测试可以模拟Clock,也可以创建虚假时钟实现。

在尝试生成MCVE并编写问题的过程中,我发现了一些有趣的事情:

在方法级别运行测试时,请注意 1 毫秒的时间戳差异。区别绝不止于此:

[START: 2015-02-26T11:53:20.581-06:00, STOP: 2015-02-26T11:53:20.641-06:00, DURATION: 0.060]    
[START: 2015-02-26T11:53:20.582-06:00, STOP: 2015-02-26T11:53:20.642-06:00, DURATION: 0.060]

但是当我运行测试最终作为套件的一部分运行时,几乎每次都会发生这种情况:

[START: 2015-02-26T12:25:31.183-06:00, STOP: 2015-02-26T12:25:31.243-06:00, DURATION: 0.060]
[START: 2015-02-26T12:25:31.183-06:00, STOP: 2015-02-26T12:25:31.243-06:00, DURATION: 0.060]

零差异。很奇怪吧?

我最好的猜测是,众所周知,JVM已经全部预热,并且在运行测试套件时达到此特定测试时会建立一些动力。如此之多,以至于实例化发生得如此之快,以至于几乎是同时发生的。从MyObject a被实例化到b被分配到b被重新分配为新MyObject之间经过的微小时间是如此之小,以至于产生具有一对相同DateTimeMyObject

事实证明,有一些可用的解决方案:

我使用的解决方案:

这和邓肯的真的很相似。在重新分配MyObject b之前调用DateTimeUtils.setCurrentMillisOffset(val),然后立即重置,因为我只需要足够长的偏移量来强制MyObjects ab之间的DateTimes差异:

@Test
@Config(configuration = TestConfig.NO_CONFIG)
public void equalityTest() {
    MyObject a = new MyObject(BigDecimal.TEN);
    MyObject b = a;
    assertThat(a.hashCode(), is(b.hashCode()));
    assertTrue(a.equals(b));
    // Force an offset
    DateTimeUtils.setCurrentMillisOffset(1000);
    b = new MyObject(BigDecimal.TEN);
    // Clears the offset
    DateTimeUtils.setCurrentMillisSystem();
    assertThat(a, is(not(b)));
}

Namshubwriter的解决方案(链接到答案(:

在整个项目和/或实际使用中可能会出现此问题的情况下,很容易成为最佳解决方案。

邓肯的解决方案(链接回答(:

通过在单元测试开始时调用 DateTimeUtils.setCurrentMillisFixed(val),然后在重新分配MyObject b之前通过调用 DateTimeUtils.setCurrentMillisFixed(val + someOffset) 强制差值来添加该时间的偏移量,从而将当前时间设置为返回固定时间。单击链接可直接跳转到包含代码的解决方案。

值得指出的是,您需要在某个时候调用DateTimeUtils.setCurrentMillisSystem()来重置时间,否则其他依赖于时间的测试可能会受到影响。

原始解决方案:

我认为这里值得一提的是,据我了解,这是唯一不依赖于在父系统上具有某些安全权限的程序的解决方案。

拨打电话Thread.sleep()确保两个MyObjectsDateTime时间戳之间存在时间间隔:

@Test
@Config(configuration = TestConfig.NO_CONFIG)
public void equalityTest() {
    MyObject a = new MyObject(BigDecimal.TEN);
    MyObject b = a;
    assertThat(a.hashCode(), is(b.hashCode()));
    assertTrue(a.equals(b));
    try {
        Thread.sleep(0, 1);
    } catch (Exception e) {
        e.printStackTrace();
    }
    b = new MyObject(BigDecimal.TEN);
    // Line that was failing
    assertThat(a, is(not(b)));
}

相关内容

最新更新