さまざまなレベルの精度でDateオブジェクトを比較する


81

ミリ秒が異なるために失敗するJUnitテストがあります。この場合、ミリ秒は気にしません。ミリ秒を無視するようにアサーションの精度を変更するにはどうすればよいですか(または設定したい精度)?

合格したい失敗したアサーションの例:

Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);
assertEquals(dateOne, dateTwo);

回答:


22

DateFormat一致させたい部分のみを表示する形式のオブジェクトを使用assertEquals()し、結果の文字列に対して実行します。それを独自のassertDatesAlmostEqual()メソッドで簡単にラップすることもできます。


15
2番目の境界でミリ秒の差がある場合は処理されません。10.000と09.999は異なります。
scarba05 2015年

61

さらに別の回避策として、次のようにします。

assertTrue("Dates aren't close enough to each other!", (date2.getTime() - date1.getTime()) < 1000);

4
分散を比較するための+1ですが、絶対分散は考慮されていません(たとえば、date1がdate2の後にある場合はどうなりますか?)
Ophidian

13
私は通常、Math.abs()でラップするだけで同様のアプローチを取ります
parxier 2012年

60

これを支援するライブラリがあります:

Apachecommons-lang

あなたが持っている場合はApache Commonsの-langのクラスパスには、使用することができますDateUtils.truncateいくつかのフィールドに日付を切り捨てること。

assertEquals(DateUtils.truncate(date1,Calendar.SECOND),
             DateUtils.truncate(date2,Calendar.SECOND));

これには省略形があります:

assertTrue(DateUtils.truncatedEquals(date1,date2,Calendar.SECOND));

12:00:00.001と11:59:00.999は異なる値に切り捨てられるため、これは理想的ではない可能性があることに注意してください。そのために、ラウンドがあります:

assertEquals(DateUtils.round(date1,Calendar.SECOND),
             DateUtils.round(date2,Calendar.SECOND));

AssertJ

バージョン3.7.0以降isCloseTo、Java 8 Date / Time APIを使用している場合、AssertJはアサーションを追加しました。

LocalTime _07_10 = LocalTime.of(7, 10);
LocalTime _07_42 = LocalTime.of(7, 42);
assertThat(_07_10).isCloseTo(_07_42, within(1, ChronoUnit.HOURS));
assertThat(_07_10).isCloseTo(_07_42, within(32, ChronoUnit.MINUTES));

また、従来のJavaDatesでも機能します。

Date d1 = new Date();
Date d2 = new Date();
assertThat(d1).isCloseTo(d2, within(100, ChronoUnit.MILLIS).getValue());

これは私が探していた解決策です:)
geoaxis 2013

1
おかげでこれは私にたくさんの時間を節約しました!
ロバートベルトラン2013年

DateUtils.roundを使用してみませんか?
domi 2014

1
ラウンドも同様に機能します。切り上げまたは切り捨てが行われますが、切り捨ては常に下がっています。パードキュメント、ラウンドも夏時間を処理します。
ダン・ワット

私は同じ問題を持っていたjava.sql.Timestampsし、DateUtils.truncate(...)Javaで私のために働いた8.私の特定のケースでは、私が保存されていたものに、メモリ内のタイムスタンプを比較したので、第二よりも任意の細かい粒の保存をサポートしていませんでしたデータベース技術を含みますデータベースから取得。インメモリタイムスタンプは、データベースから読み取られたタイムスタンプよりも精度が高かった。
ケントブル

6

あなたはこのようなことをすることができます:

assertTrue((date1.getTime()/1000) == (date2.getTime()/1000));

文字列の比較は必要ありません。


「/」対「%」の意味だと思いますか?これは、任意精度、IMHOに関して厄介になります。でも良い点です。
マイケルイースター

おっと!良いキャッチ。ただし、精度は問題ではないと思います。Date.getTime()は、エポックから常に長いミリ秒を返します。
セス

1
一方の値が3.999秒で、もう一方の値が4.000の場合、これは失敗します。つまり、最大1秒の差を許容する場合もあれば、2ミリ秒の差で失敗する場合もあります。
デヴィッド・Balažic

6

JUnitでは、次のような2つのassertメソッドをプログラムできます。

public class MyTest {
  @Test
  public void test() {
    ...
    assertEqualDates(expectedDateObject, resultDate);

    // somewhat more confortable:
    assertEqualDates("01/01/2012", anotherResultDate);
  }

  private static final String DATE_PATTERN = "dd/MM/yyyy";

  private static void assertEqualDates(String expected, Date value) {
      DateFormat formatter = new SimpleDateFormat(DATE_PATTERN);
      String strValue = formatter.format(value);
      assertEquals(expected, strValue);
  }

  private static void assertEqualDates(Date expected, Date value) {
    DateFormat formatter = new SimpleDateFormat(DATE_PATTERN);
    String strExpected = formatter.format(expected);
    String strValue = formatter.format(value);
    assertEquals(strExpected, strValue);
  }
}

4

JUnitにサポートがあるかどうかはわかりませんが、それを行う1つの方法は次のとおりです。

import java.text.SimpleDateFormat;
import java.util.Date;

public class Example {

    private static SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");

    private static boolean assertEqualDates(Date date1, Date date2) {
        String d1 = formatter.format(date1);            
        String d2 = formatter.format(date2);            
        return d1.equals(d2);
    }    

    public static void main(String[] args) {
        Date date1 = new Date();
        Date date2 = new Date();

        if (assertEqualDates(date1,date2)) { System.out.println("true!"); }
    }
}

メソッドを呼び出すと、assertEqualDates戻り値の型voidを作成し、最後の行を作成しassertEquals(d1, d2)ます。このようにすると、すべてのJUnitassert*メソッドと同じように動作します。
Joachim Sauer

同意しました。コードを実行したかったのですが、JUnitが手元にありませんでした。
マイケルイースター

1
グローバルな日付フォーマッターには注意してください。それらはスレッドセーフではありません。このコードでは問題ありませんが、悪い習慣があります。
itsadok 2009年

1
これは、2つのDateオブジェクトに1秒未満の差があるが、2番目のしきい値を超えている場合には対処しません。
オフィディアン2010

3

これは、気にしない分散がチェックしている値のしきい値を超える境界ケースがあるため、実際には見た目よりも難しい問題です。たとえば、ミリ秒の差は1秒未満ですが、2つのタイムスタンプが2番目のしきい値、または分のしきい値、または時間のしきい値を超えています。これにより、DateFormatアプローチは本質的にエラーが発生しやすくなります。

代わりに、実際のミリ秒のタイムスタンプを比較し、2つの日付オブジェクト間の許容可能な差異を示す分散デルタを提供することをお勧めします。過度に冗長な例は次のとおりです。

public static void assertDateSimilar(Date expected, Date actual, long allowableVariance)
{
    long variance = Math.abs(allowableVariance);

    long millis = expected.getTime();
    long lowerBound = millis - allowableVariance;
    long upperBound = millis + allowableVariance;

    DateFormat df = DateFormat.getDateTimeInstance();

    boolean within = lowerBound <= actual.getTime() && actual.getTime() <= upperBound;
    assertTrue(MessageFormat.format("Expected {0} with variance of {1} but received {2}", df.format(expected), allowableVariance, df.format(actual)), within);
}

2

JUnit 4を使用すると、選択した精度に従って日付をテストするためのマッチャーを実装することもできます。この例では、マッチャーは文字列形式の式をパラメーターとして受け取ります。この例のコードはこれ以上短くはありません。ただし、マッチャークラスは再利用できます。また、説明的な名前を付けると、テストで意図をエレガントな方法で文書化できます。

import static org.junit.Assert.assertThat;
// further imports from org.junit. and org.hamcrest.

@Test
public void testAddEventsToBaby() {
    Date referenceDate = new Date();
    // Do something..
    Date testDate = new Date();

    //assertThat(referenceDate, equalTo(testDate)); // Test on equal could fail; it is a race condition
    assertThat(referenceDate, sameCalendarDay(testDate, "yyyy MM dd"));
}

public static Matcher<Date> sameCalendarDay(final Object testValue, final String dateFormat){

    final SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);

    return new BaseMatcher<Date>() {

        protected Object theTestValue = testValue;


        public boolean matches(Object theExpected) {
            return formatter.format(theExpected).equals(formatter.format(theTestValue));
        }

        public void describeTo(Description description) {
            description.appendText(theTestValue.toString());
        }
    };
}

2

Joda-TimeにAssertJアサーションを使用します(http://joel-costigliola.github.io/assertj/assertj-joda-time.html

import static org.assertj.jodatime.api.Assertions.assertThat;
import org.joda.time.DateTime;

assertThat(new DateTime(dateOne.getTime())).isEqualToIgnoringMillis(new DateTime(dateTwo.getTime()));

テスト失敗メッセージが読みやすくなります

java.lang.AssertionError: 
Expecting:
  <2014-07-28T08:00:00.000+08:00>
to have same year, month, day, hour, minute and second as:
  <2014-07-28T08:10:00.000+08:00>
but had not.

1
AssertJはjava.util.dateでも機能します。– assertThat(new Date(2016 - 1900, 0, 1,12,13,14)).isEqualToIgnoringMillis("2016-01-01T12:13:14");
Dan Watt

1

Jodaを使用している場合は、Fest JodaTimeを使用できます。


3
これをどのように実装すべきかについて、より多くの情報を提供できますか?それ以外の場合は、コメントに変換する必要があります。
Hugo Dozois 2013

1

比較したい日付部分を比較するだけです。

Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);

assertEquals(dateOne.getMonth(), dateTwo.getMonth());
assertEquals(dateOne.getDate(), dateTwo.getDate());
assertEquals(dateOne.getYear(), dateTwo.getYear());

// alternative to testing with deprecated methods in Date class
Calendar calOne = Calendar.getInstance();
Calendar calTwo = Calendar.getInstance();
calOne.setTime(dateOne);
calTwo.setTime(dateTwo);

assertEquals(calOne.get(Calendar.MONTH), calTwo.get(Calendar.MONTH));
assertEquals(calOne.get(Calendar.DATE), calTwo.get(Calendar.DATE));
assertEquals(calOne.get(Calendar.YEAR), calTwo.get(Calendar.YEAR));

私はこのアプローチが日付フォーマッターを使用するよりもずっと好きです。唯一の問題は、Dateの特定のゲッターフィールドが非推奨になることです。同じことを行うには、カレンダーを使用することをお勧めします。
kfox 2013

ああ、これらのメソッドは非推奨であることに注意してください。代わりにCalendarオブジェクトを変換して比較するための代替コードで回答を更新しました。
オリバーヘルナンデス

1

JUnitには、doubleを比較し、それらをどれだけ近づける必要があるかを指定するためのアサーションが組み込まれています。この場合、デルタは、日付が同等であると見なすミリ秒以内です。このソリューションには境界条件がなく、絶対分散を測定し、精度を簡単に指定でき、追加のライブラリやコードを記述する必要がありません。

    Date dateOne = new Date();
    dateOne.setTime(61202516585000L);
    Date dateTwo = new Date();
    dateTwo.setTime(61202516585123L);
    // this line passes correctly 
    Assert.assertEquals(dateOne.getTime(), dateTwo.getTime(), 500.0);
    // this line fails correctly
    Assert.assertEquals(dateOne.getTime(), dateTwo.getTime(), 100.0);

それらをdoubleとして比較するように強制するには、100ではなく100.0である必要があります(またはdoubleへのキャストが必要です)。


1

日付を比較するときに必要な精度レベルを選択できます。例:

LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
// e.g. in MySQL db "timestamp" is without fractional seconds precision (just up to seconds precision)
assertEquals(myTimestamp, now);

0

このようなものが機能する可能性があります:

assertEquals(new SimpleDateFormat("dd MMM yyyy").format(dateOne),
                   new SimpleDateFormat("dd MMM yyyy").format(dateTwo));

0

new Date直接使用する代わりに、テストでモックアウトできる小さなコラボレーターを作成できます。

public class DateBuilder {
    public java.util.Date now() {
        return new java.util.Date();
    }
}

DateBuilderメンバーを作成し、呼び出しをからnew Dateに変更しますdateBuilder.now()

import java.util.Date;

public class Demo {

    DateBuilder dateBuilder = new DateBuilder();

    public void run() throws InterruptedException {
        Date dateOne = dateBuilder.now();
        Thread.sleep(10);
        Date dateTwo = dateBuilder.now();
        System.out.println("Dates are the same: " + dateOne.equals(dateTwo));
    }

    public static void main(String[] args) throws InterruptedException {
        new Demo().run();
    }
}

主な方法は以下を生成します:

Dates are the same: false

テストでは、のスタブを挿入して、DateBuilder任意の値を返すようにすることができます。たとえば、Mockitoまたは以下をオーバーライドする匿名クラスを使用しますnow()

public class DemoTest {

    @org.junit.Test
    public void testMockito() throws Exception {
        DateBuilder stub = org.mockito.Mockito.mock(DateBuilder.class);
        org.mockito.Mockito.when(stub.now()).thenReturn(new java.util.Date(42));

        Demo demo = new Demo();
        demo.dateBuilder = stub;
        demo.run();
    }

    @org.junit.Test
    public void testAnonymousClass() throws Exception {
        Demo demo = new Demo();
        demo.dateBuilder = new DateBuilder() {
            @Override
            public Date now() {
                return new Date(42);
            }
        };
        demo.run();
    }
}

0

SimpleDateFromatを使用して日付を文字列に変換し、コンストラクターで必要な日付/時刻フィールドを指定して、文字列値を比較します。

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String expectedDate = formatter.format(dateOne));
String dateToTest = formatter.format(dateTwo);
assertEquals(expectedDate, dateToTest);

0

私はここで終わるいくつかのグーグルに役立つかもしれない小さなクラスをしました:https://stackoverflow.com/a/37168645/5930242


0

これが私のために仕事をしたユーティリティ関数です。

    private boolean isEqual(Date d1, Date d2){
        return d1.toLocalDate().equals(d2.toLocalDate());
    }


-1

オブジェクトをjava.util.Dateにキャストして比較します

assertEquals((Date)timestamp1,(Date)timestamp2);

これにより、精度が原因でアサートが失敗します。
TechCrunch 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.