Статьи

Дразнить с DateTime JodaTime и поставщиком Google Guava

Вступление

Если вы опытный юнит-тестер, вы научились делать заметки, когда видите, что любой код работает со временем , параллелизмом , случайностью , постоянством и дисковым вводом / выводом .

Причиной этого является то, что тесты могут быть очень хрупкими, а иногда совершенно невозможными для правильного тестирования. Этот пост покажет, как абстрагироваться от «времени» путем введения его замены потребителю. Эта публикация будет использовать Spring 3 в качестве контейнера внедрения зависимостей, хотя Guice , другие DI-контейнеры или конструкторы / установщики в POJO также будут работать. Я также буду игнорировать Locales, поскольку основное внимание уделяется внедрению DateTime, а не самой DateTime.

Существующий код

Вам передали кусок кода для модульного тестирования (или вы его создаете, и это ваш первый удар по нему). Наш первый фрагмент кода, только один класс: (Этот класс является контроллером Spring 3.1, и его цель — вернуть текущее время в виде строки)

01
02
03
04
05
06
07
08
09
10
11
12
13
@Controller
@RequestMapping(value = '/time')
@VisibleForTesting
class TimeController {
 
    @RequestMapping(value = '/current', method = RequestMethod.GET)
    @ResponseBody
    public String showCurrentTime() {
        // BAD!!! Can't test
        DateTime dateTime = new DateTime();
        return DateTimeFormat.forPattern('hh:mm').print(dateTime);
    }
}

Обратите внимание, что класс делает «новый DateTime ()» в классе. Вот соответствующий тестовый класс:

Что происходит, когда мы запускаем тест? Как насчет предположения, что у нас очень медленная машина? Вы можете (и, скорее всего, так и будет) в итоге ваш DateTime сравнения будет отличаться от возвращенного DateTime . Это проблема!

Первое, что нужно сделать, это удалить зависимость, но как мы собираемся это сделать? Если мы сделаем DateTime полем в классе, у нас все равно будет та же проблема. Представьте интерфейс поставщика Google Guava.

Google Guava Supplier

Интерфейс поставщика имеет только один метод, get (), который возвращает экземпляр того, для чего настроен поставщик. Например, поставщик вернет имя пользователя, если он вошел в систему, и имя по умолчанию, если они этого не сделали:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class FirstNameSupplier implements Supplier<String> {
 
    private String value;
    private static final String DEFAULT_NAME = 'GUEST';
 
    public FirstNameSupplier() {
        // Just believe that this goes and gets a User from somewhere
        String firstName = UserUtilities.getUser().getFirstName();
        // more Guava
        if(isNullOrEmpty(firstName)) {
            value = DEFAULT_NAME;
        } else {
            value = firstName;
        }
    }
 
    @Override
    public String get() {
        return value;
    }
}

Что касается вашего метода реализации, вам все равно, какое имя у вас есть, только то, что вы его получили.

Рефакторинг DateTime

Давайте двигаться дальше. Для гораздо более реального примера использования поставщика (и смысл этого поста) давайте реализуем поставщика DateTime, чтобы вернуть нам текущий DateTime . Пока мы на этом, давайте также создадим интерфейс, чтобы мы могли создавать фиктивные реализации для тестирования:

1
2
3
public interface DateTimeSupplier extends Supplier<DateTime> {
    DateTime get();
}

и реализация:

1
2
3
4
5
6
public class DateTimeUTCSupplier implements DateTimeSupplier {
    @Override
    public DateTime get() {
        return new DateTime(DateTimeZone.UTC);
    }
}

Теперь мы можем взять DateTimeUTCSupplier и вставить это в наш код, которому нужен текущий DateTime в качестве интерфейса DateTimeSupplier :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Controller
@RequestMapping(value = '/time')
@VisibleForTesting
class TimeController {
 
    @Autowired
    @VisibleForTesting
    // Injected DateTimeSupplier
    DateTimeSupplier dateTime;
 
    @RequestMapping(value = '/current', method = RequestMethod.GET)
    @ResponseBody
    public String showCurrentTime() {
        return DateTimeFormat.forPattern('hh:mm').print(dateTime.get());
    }
}

Чтобы проверить это, нам нужно создать MockDateTimeSupplier и иметь контроллер для передачи в конкретный экземпляр, который мы хотим вернуть:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class MockDateTimeSupplier implements DateTimeSupplier {
 
    private final DateTime mockedDateTime;
 
    public MockDateTimeSupplier(DateTime mockedDateTime) {
        this.mockedDateTime = mockedDateTime;
    }
 
    @Override
    public DateTime get() {
        return mockedDateTime;
    }
}

Обратите внимание, что сохраняемый объект передается через конструктор. Это не вернет вам текущую дату / время, но вернет конкретный экземпляр, который вы хотите

и наконец наш тест, который использует (немного) TimeController, который мы реализовали выше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class TimeControllerTest {
 
    private final int HOUR_OF_DAY = 12;
    private final int MINUTE_OF_DAY = 30;
 
    @Test
    public void testShowCurrentTime() throws Exception {
        TimeController controller = new TimeController();
        // Create the mock DateTimeSupplier with our known DateTime
        controller.dateTime = new MockDateTimeSupplier(new DateTime(2012, 1, 1, HOUR_OF_DAY, MINUTE_OF_DAY, 0, 0));
 
        // Call our method
        String dateTimeString = controller.showCurrentTime();
 
        // Using hamcrest for easier to read assertions and condition matchers
        assertThat(dateTimeString, is(String.format('%d:%d', HOUR_OF_DAY, MINUTE_OF_DAY)));
    }
 
}

Вывод

В этом посте показано, как использовать интерфейс поставщика Google Guava для абстрагирования объекта DateTime, чтобы вы могли лучше проектировать свои реализации с учетом модульного тестирования! Поставщики — отличный способ решить «просто дай мне что-нибудь», учтите, это известный тип чего-то.

Приятного кодирования и не забудьте поделиться!

Ссылка: Насмешка с DateTime JodaTime и Поставщиком Google Guava от нашего партнера JCG Майка в блоге сайта Майка .