Сравнить объекты даты с различными уровнями точности
У меня есть тест JUnit, который терпит неудачу, потому что миллисекунды разные. В этом случае меня не волнует миллисекунды. Как я могу изменить точность assert для игнорирования миллисекунд (или любой точности, которую я хотел бы установить)?
Пример неудачного утверждения, которое я хотел бы передать:
Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);
assertEquals(dateOne, dateTwo);
Ответы
Ответ 1
Используйте объект DateFormat
с форматом, который отображает только те части, которые вы хотите сопоставить, и выполните assertEquals()
в результирующих строках, Вы также можете легко обернуть это в свой собственный метод assertDatesAlmostEqual()
.
Ответ 2
Еще одно обходное решение, я бы сделал это следующим образом:
assertTrue("Dates aren't close enough to each other!", (date2.getTime() - date1.getTime()) < 1000);
Ответ 3
Существуют библиотеки, которые помогают с этим:
Apache commons-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, AssertJ добавил isCloseTo
, если вы используете API-интерфейс Java 8 Date/Time.
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));
Он также работает с устаревшими датами java:
Date d1 = new Date();
Date d2 = new Date();
assertThat(d1).isCloseTo(d2, within(100, ChronoUnit.MILLIS).getValue());
Ответ 4
Вы можете сделать что-то вроде этого:
assertTrue((date1.getTime()/1000) == (date2.getTime()/1000));
Нет необходимости в сравнении строк.
Ответ 5
В JUnit вы можете запрограммировать два метода 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);
}
}
Ответ 6
Я не знаю, есть ли поддержка в JUnit, но один из способов сделать это:
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!"); }
}
}
Ответ 7
На самом деле проблема сложнее, чем из-за граничных случаев, когда разница, которую вы не заботитесь, пересекает порог для значения, которое вы проверяете. например разница в миллисекундах меньше секунды, но две временные метки пересекают второй порог или минутный порог или часовой порог. Это делает любой подход DateFormat неотъемлемо подверженным ошибкам.
Вместо этого я бы предложил сравнить фактические миллисекундные метки времени и предоставить дельта дисперсии, указывающую, что вы считаете приемлемой разницей между двумя объектами даты. Из приведенного выше подробного примера следует:
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);
}
Ответ 8
Используя JUnit 4, вы также можете реализовать совпадение для тестирования дат в соответствии с выбранной вами точностью. В этом примере matcher принимает выражение в виде строки в качестве параметра. Для этого примера код не является короче. Однако класс сопряжения может быть повторно использован; и если вы дадите ему описательное имя, вы можете запечатлеть намерение с тестом элегантным способом.
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());
}
};
}
Ответ 9
использовать утверждения AssertJ для Joda-Time (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.
Ответ 10
Просто сравните детали, которые вы хотите сравнить:
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));
Ответ 11
Если вы использовали Joda, вы можете использовать Fest Joda Time.
Ответ 12
JUnit имеет встроенное утверждение для сравнения удвоений и указание того, насколько близко они должны быть. В этом случае дельта находится в пределах того, сколько миллисекунд, которые вы считаете датами, эквивалентными. Это решение не имеет граничных условий, измеряет абсолютную дисперсию, может легко определять точность и не требует добавления дополнительных библиотек или кода.
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);
Примечание Это должно быть 100.0 вместо 100 (или требуется бросок, чтобы удвоить), чтобы заставить их сравнивать их как двойные.
Ответ 13
Что-то вроде этого может работать:
assertEquals(new SimpleDateFormat("dd MMM yyyy").format(dateOne),
new SimpleDateFormat("dd MMM yyyy").format(dateTwo));
Ответ 14
Вместо непосредственного использования 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();
}
}
Ответ 15
Преобразуйте даты в String с помощью SimpleDateFromat, укажите в конструкторе необходимые поля даты и времени и сравните строковые значения:
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String expectedDate = formatter.format(dateOne));
String dateToTest = formatter.format(dateTwo);
assertEquals(expectedDate, dateToTest);
Ответ 16
Я сделал небольшой класс, который может быть полезен для некоторых googlers, которые здесь: fooobar.com/questions/96663/...
Ответ 17
Вы можете выбрать уровень точности при сравнении дат, например:
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);
Ответ 18
Вот полезная функция, которая сделала эту работу за меня.
private boolean isEqual(Date d1, Date d2){
return d1.toLocalDate().equals(d2.toLocalDate());
}
Ответ 19
я отбрасываю объекты в java.util.Date и сравниваю
assertEquals((Date)timestamp1,(Date)timestamp2);