Если вы опытный юнит-тестер, вы научились делать заметки, когда видите, что любой код работает со временем , параллелизмом , случайностью , постоянством и дисковым вводом / выводом .
Причиной этого является то, что тесты могут быть очень хрупкими, а иногда совершенно невозможными для правильного тестирования. Этот пост покажет, как абстрагироваться от «времени» путем введения его замены потребителю. Эта публикация будет использовать 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 Майка в блоге сайта Майка .