Inleiding tot Awaitility

1. Inleiding

Een veelvoorkomend probleem met asynchrone systemen is dat het moeilijk is om leesbare tests voor hen te schrijven die gericht zijn op bedrijfslogica en niet vervuild zijn met synchronisaties, time-outs en gelijktijdigheidscontrole.

In dit artikel gaan we het bekijken Awaitility - een bibliotheek die een eenvoudige domeinspecifieke taal (DSL) biedt voor het testen van asynchrone systemen.

Met Awaitility kunnen we dat onze verwachtingen van het systeem uitdrukken in een gemakkelijk te lezen DSL.

2. Afhankelijkheden

We moeten Awaitility-afhankelijkheden toevoegen aan onze pom.xml.

De afwachten bibliotheek is voldoende voor de meeste gevallen. Voor het geval we proxy-gebaseerde voorwaarden willen gebruiken, we moeten ook de awaitility-proxy bibliotheek:

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

U kunt de nieuwste versie van het afwachten en awaitility-proxy bibliotheken op Maven Central.

3. Een asynchrone service maken

Laten we een eenvoudige asynchrone service schrijven en deze testen:

openbare klasse AsyncService {privé finale int DELAY = 1000; privé finale int INIT_DELAY = 2000; private AtomicLong-waarde = nieuwe AtomicLong (0); private Executor executor = Executors.newFixedThreadPool (4); private vluchtige boolean geïnitialiseerd = false; void initialize () {executor.execute (() -> {sleep (INIT_DELAY); geïnitialiseerd = true;}); } boolean isInitialized () {return geïnitialiseerd; } void addValue (long val) {throwIfNotInitialized (); executor.execute (() -> {sleep (DELAY); value.addAndGet (val);}); } openbaar lang getValue () {throwIfNotInitialized (); retourwaarde.longValue (); } privé leegte slaap (int vertraging) {probeer {Thread.sleep (vertraging); } catch (InterruptedException e) {}} private void throwIfNotInitialized () {if (! initialized) {throw new IllegalStateException ("Service is niet geïnitialiseerd"); }}}

4. Testen met afwachtendheid

Laten we nu de testklasse maken:

openbare klasse AsyncServiceLongRunningManualTest {privé AsyncService asyncService; @Before public void setUp () {asyncService = nieuwe AsyncService (); } // ...}

Onze test controleert of de initialisatie van onze service plaatsvindt binnen een bepaalde time-outperiode (standaard 10 seconden) na het aanroepen van het initialiseren methode.

Deze testcase wacht slechts tot de service-initialisatiestatus verandert of gooit een ConditionTimeoutException als de toestandverandering niet optreedt.

De status wordt verkregen door a Oproepbaar die onze service met gedefinieerde intervallen (standaard 100 ms) ondervraagt ​​na een gespecificeerde initiële vertraging (standaard 100 ms). Hier gebruiken we de standaardinstellingen voor de time-out, interval en vertraging:

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

Hier gebruiken we wachten - een van de statische methoden van de Wachtbaarheid klasse. Het retourneert een instantie van een ConditionFactory klasse. We kunnen ook andere methoden gebruiken, zoals gegeven om de leesbaarheid te vergroten.

De standaard timingparameters kunnen worden gewijzigd met behulp van statische methoden van de Wachtbaarheid klasse:

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

Hier kunnen we het gebruik van de Looptijd class, die nuttige constanten biedt voor de meest gebruikte tijdsperioden.

We kunnen ook geef voor elk aangepaste timingwaarden op wachten bellen. Hier verwachten we dat de initialisatie maximaal na vijf seconden zal plaatsvinden en in ieder geval na 100 ms met polling-intervallen van 100 ms:

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

Het is vermeldenswaard dat de ConditionFactory bevat aanvullende methoden zoals met, dan, en, gegeven. Deze methoden doen niets en keren gewoon terug dit, maar ze kunnen nuttig zijn om de leesbaarheid van testomstandigheden te verbeteren.

5. Matchers gebruiken

Awaitility maakt ook het gebruik van hamcrest matchers om het resultaat van een uitdrukking te controleren. We kunnen bijvoorbeeld controleren of onze lang waarde wordt zoals verwacht gewijzigd na het aanroepen van de waarde toevoegen methode:

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

Merk op dat we in dit voorbeeld de eerste hebben gebruikt wachten bel om te wachten tot de service is geïnitialiseerd. Anders de getValue methode zou een IllegalStateException.

6. Uitzonderingen negeren

Soms hebben we een situatie waarin een methode een uitzondering genereert voordat een asynchrone taak is voltooid. In onze dienst kan het een telefoontje zijn naar de getValue methode voordat de service wordt geïnitialiseerd.

Awaitility biedt de mogelijkheid om deze uitzondering te negeren zonder een test te mislukken.

Laten we bijvoorbeeld controleren of het getValue resultaat is gelijk aan nul direct na initialisatie, negerend IllegalStateException:

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

7. Proxy gebruiken

Zoals beschreven in sectie 2, moeten we opnemen awaitility-proxy om proxy-gebaseerde voorwaarden te gebruiken. Het idee van proxy is om te voorzien in echte methodecondities zonder implementatie van een Oproepbaar of lambda-uitdrukking.

Laten we de AwaitilityClassProxy.to statische methode om dat te controleren AsyncService is geïnitialiseerd:

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

8. Toegang tot velden

Awaitility heeft zelfs toegang tot privévelden om er beweringen op uit te voeren. In het volgende voorbeeld kunnen we een andere manier zien om de initialisatiestatus van onze service te krijgen:

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

9. Conclusie

In deze korte tutorial hebben we de Awaitility-bibliotheek geïntroduceerd, hebben we kennis gemaakt met de basis-DSL voor het testen van asynchrone systemen en hebben we enkele geavanceerde functies gezien die de bibliotheek flexibel en gemakkelijk te gebruiken maken in echte projecten.

Zoals altijd zijn alle codevoorbeelden beschikbaar op Github.