Въведение в Awaitility

1. Въведение

Често срещан проблем при асинхронните системи е, че е трудно да се напишат четливи тестове за тях, които са фокусирани върху бизнес логиката и не са замърсени със синхронизации, изчаквания и контрол на паралелността.

В тази статия ще разгледаме Awaitility - библиотека, която предоставя прост специфичен за домейна език (DSL) за тестване на асинхронни системи .

С Awaitility можем да изразим очакванията си от системата в лесен за четене DSL.

2. Зависимости

Трябва да добавим зависимости Awaitility към нашия pom.xml.

В awaitility библиотеката ще бъде достатъчно за повечето случаи на употреба. В случай, че искаме да използваме условия , базирани на прокси , също трябва да предоставим библиотеката awaitility-proxy :

 org.awaitility awaitility 3.0.0 test   org.awaitility awaitility-proxy 3.0.0 test 

Можете да намерите най-новата версия на awaitility и awaitility-прокси библиотеките на Maven Central.

3. Създаване на асинхронна услуга

Нека напишем проста асинхронна услуга и да я тестваме:

public class AsyncService { private final int DELAY = 1000; private final int INIT_DELAY = 2000; private AtomicLong value = new AtomicLong(0); private Executor executor = Executors.newFixedThreadPool(4); private volatile boolean initialized = false; void initialize() { executor.execute(() -> { sleep(INIT_DELAY); initialized = true; }); } boolean isInitialized() { return initialized; } void addValue(long val) { throwIfNotInitialized(); executor.execute(() -> { sleep(DELAY); value.addAndGet(val); }); } public long getValue() { throwIfNotInitialized(); return value.longValue(); } private void sleep(int delay) { try { Thread.sleep(delay); } catch (InterruptedException e) { } } private void throwIfNotInitialized() { if (!initialized) { throw new IllegalStateException("Service is not initialized"); } } }

4. Тестване с очакване

Сега нека създадем тестовия клас:

public class AsyncServiceLongRunningManualTest { private AsyncService asyncService; @Before public void setUp() { asyncService = new AsyncService(); } //... }

Нашият тест проверява дали инициализацията на нашата услуга се случва в рамките на определен период на изчакване (по подразбиране 10 секунди) след извикване на метода за инициализиране .

Този тестов случай само чака състоянието за инициализация на услугата да се промени или изхвърля ConditionTimeoutException, ако промяната на състоянието не настъпи.

Състоянието се получава от Callable, който анкетира нашата услуга на определени интервали (100ms по подразбиране) след определено първоначално закъснение (100ms по подразбиране). Тук използваме настройките по подразбиране за времето за изчакване, интервала и закъснението:

asyncService.initialize(); await() .until(asyncService::isInitialized);

Ето, ние използваме очакват - един от най-статичните методи на Awaitility клас. Той връща екземпляр на клас ConditionFactory . Можем да използваме и други методи като дадени, за да се увеличи четливостта.

Параметрите на времето по подразбиране могат да се променят с помощта на статични методи от класа Awaitility :

Awaitility.setDefaultPollInterval(10, TimeUnit.MILLISECONDS); Awaitility.setDefaultPollDelay(Duration.ZERO); Awaitility.setDefaultTimeout(Duration.ONE_MINUTE);

Тук можем да видим използването на класа Duration , който предоставя полезни константи за най-често използваните периоди от време.

Също така можем да предоставим персонализирани стойности за синхронизация за всяко изчакващо повикване . Тук очакваме, че инициализацията ще настъпи най-много след пет секунди и най-малко след 100 ms с интервали на анкетиране от 100 ms:

asyncService.initialize(); await() .atLeast(Duration.ONE_HUNDRED_MILLISECONDS) .atMost(Duration.FIVE_SECONDS) .with() .pollInterval(Duration.ONE_HUNDRED_MILLISECONDS) .until(asyncService::isInitialized);

Това е да се спомене, че ConditionFactory съдържа допълнителни методи като с , а след това , и , дал. Тези методи не правят нищо и просто връщат това , но биха могли да бъдат полезни за подобряване на четливостта на условията на теста.

5. Използване на съвпадения

Awaitility също така позволява използването на съвпадения на подложките за проверка на резултата от израз. Например, можем да проверим дали нашата дълга стойност се променя според очакванията след извикване на метода addValue :

asyncService.initialize(); await() .until(asyncService::isInitialized); long value = 5; asyncService.addValue(value); await() .until(asyncService::getValue, equalTo(value));

Имайте предвид, че в този пример използвахме първото изчакване, за да изчакаме, докато услугата бъде инициализирана. В противен случай методът getValue би хвърлил IllegalStateException .

6. Игнориране на изключенията

Понякога имаме ситуация, при която методът хвърля изключение преди да бъде извършена асинхронна работа. В нашата услуга това може да бъде извикване на метода getValue , преди услугата да бъде инициализирана.

Очакването предоставя възможност за игнориране на това изключение, без да се провали тест.

Например, нека проверим дали резултатът getValue е равен на нула веднага след инициализацията, пренебрегвайки IllegalStateException :

asyncService.initialize(); given().ignoreException(IllegalStateException.class) .await().atMost(Duration.FIVE_SECONDS) .atLeast(Duration.FIVE_HUNDRED_MILLISECONDS) .until(asyncService::getValue, equalTo(0L));

7. Използване на прокси

Както е описано в раздел 2, трябва да включим awaitility-proxy, за да използваме условия, базирани на прокси. Идеята на проксирането е да осигури реални извиквания на метод за условия без изпълнение на Callable или lambda израз.

Нека използваме статичния метод AwaitilityClassProxy.to , за да проверим дали AsyncService е инициализиран:

asyncService.initialize(); await() .untilCall(to(asyncService).isInitialized(), equalTo(true));

8. Достъп до полета

Awaitility може дори да осъществява достъп до частни полета, за да изпълнява твърдения върху тях. В следващия пример можем да видим друг начин за получаване на състоянието на инициализация на нашата услуга:

asyncService.initialize(); await() .until(fieldIn(asyncService) .ofType(boolean.class) .andWithName("initialized"), equalTo(true));

9. Заключение

В този бърз урок представихме библиотеката Awaitility, запознахме се с нейния основен DSL за тестване на асинхронни системи и видяхме някои усъвършенствани функции, които правят библиотеката гъвкава и лесна за използване в реални проекти.

Както винаги, всички примери за код са достъпни на Github.