Сравнение объектов даты с различными уровнями точности
У меня есть тест JUnit, который терпит неудачу, потому что миллисекунды разные. В этом случае меня не волнуют миллисекунды. Как я могу изменить точность утверждения, чтобы игнорировать миллисекунды (или любую точность, которую я хотел бы установить)?
пример неудачного утверждения, которое я хотел бы передать:
Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);
assertEquals(dateOne, dateTwo);
18 ответов:
использовать
DateFormat
объект с формой, которая показывает только те части, которые вы хотите сопоставить и сделатьassertEquals()
в результирующей строке. Вы также можете легко обернуть это в свой собственныйassertDatesAlmostEqual()
метод.
еще один обходной путь, я бы сделал это так:
assertTrue("Dates aren't close enough to each other!", (date2.getTime() - date1.getTime()) < 1000);
есть библиотеки, которые помогают в этом:
Apache commons-lang
если у вас Apache commons-lang на вашем classpath, вы можете использовать
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
утверждения, если вы используете Java 8 Date / Time API.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());
вы могли бы сделать что-то вроде этого:
assertTrue((date1.getTime()/1000) == (date2.getTime()/1000));
сравнение строк не требуется.
в 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); } }
Я не знаю, есть ли поддержка в 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!"); } } }
на самом деле это более сложная проблема, чем кажется, из-за граничных случаев, когда дисперсия, о которой вы не заботитесь, пересекает порог для значения, которое вы проверяете. например, разница в миллисекундах меньше секунды, но две метки времени пересекают второй порог, или минутный порог, или часовой порог. Это делает любой подход DateFormat по своей сути подверженным ошибкам.
вместо этого я бы предложил сравнить фактические миллисекундные временные метки и предоставить Дельта дисперсии указывает, что вы считаете приемлемой разницей между двумя объектами date. Далее следует чрезмерно подробный пример:
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); }
С помощью JUnit 4 вы также можете реализовать matcher для дат испытания согласно вашей выбранной точности. В этом примере сопоставитель принимает строковое выражение формата в качестве параметра. Код не короче для этого примера. Однако класс 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()); } }; }
используйте утверждения 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.
Если бы вы использовали Joda, вы могли бы использовать Fest Joda Time.
просто сравните части даты, которые вы заинтересованы в сравнении:
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));
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 (или бросок удвоить необходимо), чтобы заставить его сравнить их как удваивает.
что то вроде этого должно работать:
assertEquals(new SimpleDateFormat("dd MMM yyyy").format(dateOne), new SimpleDateFormat("dd MMM yyyy").format(dateTwo));
вместо
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(); } }
преобразуйте даты в строку с помощью SimpleDateFromat, укажите в конструкторе необходимые поля даты / времени и сравните строковые значения:
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String expectedDate = formatter.format(dateOne)); String dateToTest = formatter.format(dateTwo); assertEquals(expectedDate, dateToTest);
Я сделал небольшой класс, который может быть полезен для некоторых гуглеров, которые в конечном итоге здесь:https://stackoverflow.com/a/37168645/5930242