簡體   English   中英

比較DateTimes的JUnit測試方法僅在從套件運行時失敗

[英]JUnit test method comparing DateTimes fails only when run from suites

在JUnit 4.11下運行的單個JUnit測試在大多數情況下通過模塊測試套件運行40次運行:2次失敗,38次通過 )或類測試套件( 40次運行:6次失敗,34次通過 ) ,但運行測試方法本身不會產生單一故障( 50次運行:0次失敗,50次通過 )。

總結所發生的事情,在equals(Object MyObject)實現返回true如果org.joda.time.DateTime對應鍵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 ab之間經過的微小時間被分配,直到b被重新分配為新的MyObject是如此微小,以至於產生具有相同DateTime對的MyObject

事實證明,有一些可用的解決方案:

我解決的解決方案:

這與鄧肯非常相似。 在重新分配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)來返回固定時間,然后通過調用DateTimeUtils.setCurrentMillisFixed(val + someOffset)在重新分配MyObject b之前強制差異來為該時間添加偏移量。 單擊鏈接以使用代碼直接跳轉到他的解決方案。

值得指出的是,您需要在某個時刻調用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)));
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM