JUnit 4.11 下运行的单个 JUnit 测试在通过模块测试套件(40 次运行:2 次失败,38 次通过(或类测试套件(40 次运行:6 次失败,34 次通过(运行时大部分时间都失败,但运行测试方法本身不会产生单个失败(50 次运行: 0 次失败,50 次通过(。
总结一下正在发生的事情,如果当前实例与传递给该方法的实例中的org.joda.time.DateTime
相同,则equals(Object MyObject)
实现返回true
键Stamp.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
之间经过的微小时间是如此之小,以至于产生具有一对相同DateTime
的MyObject
。
事实证明,有一些可用的解决方案:
我使用的解决方案:
这和邓肯的真的很相似。在重新分配MyObject b
之前调用DateTimeUtils.setCurrentMillisOffset(val)
,然后立即重置,因为我只需要足够长的偏移量来强制MyObjects
a
和b
之间的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()
确保两个MyObjects
的DateTime
时间戳之间存在时间间隔:
@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)));
}