Een abstracte les testen met JUnit

1. Overzicht

In deze tutorial analyseren we verschillende use-cases en mogelijke alternatieve oplossingen voor het testen van eenheden van abstracte klassen met niet-abstracte methoden.

Let daar op het testen van abstracte klassen zou bijna altijd via de openbare API van de concrete implementaties moeten gaan, pas de onderstaande technieken dus alleen toe als u zeker weet wat u doet.

2. Maven afhankelijkheden

Laten we beginnen met Maven-afhankelijkheden:

 org.junit.jupiter junit-jupiter-engine 5.1.0 test org.mockito mockito-core 2.8.9 test org.powermock powermock-module-junit4 1.7.4 test junit junit org.powermock powermock-api-mockito2 1.7.4 test 

U kunt de nieuwste versies van deze bibliotheken vinden op Maven Central.

Powermock wordt niet volledig ondersteund voor Junit5. Ook, powermock-module-junit4 wordt slechts gebruikt voor één voorbeeld dat wordt gepresenteerd in sectie 5.

3. Onafhankelijke niet-abstracte methode

Laten we eens kijken naar een geval waarin we een abstracte klasse hebben met een openbare niet-abstracte methode:

openbare abstracte klasse AbstractIndependent {openbare abstracte int abstractFunc (); public String defaultImpl () {return "DEFAULT-1"; }}

We willen de methode testen defaultImpl (), en we hebben twee mogelijke oplossingen: een concrete klasse gebruiken of Mockito gebruiken.

3.1. Met behulp van een Concrete Class

Maak een concrete klasse die zich uitbreidt AbstractOnafhankelijk class, en gebruik het om de methode te testen:

openbare klasse ConcreteImpl breidt AbstractIndependent {@Override public int abstractFunc () {return 4; }}
@Test openbare leegte gegevenNonAbstractMethod_whenConcreteImpl_testCorrectBehaviour () {ConcreteImpl conClass = nieuwe ConcreteImpl (); String actual = conClass.defaultImpl (); assertEquals ("DEFAULT-1", actueel); }

Het nadeel van deze oplossing is de noodzaak om de concrete class te maken met dummy-implementaties van alle abstracte methoden.

3.2. Mockito gebruiken

Als alternatief kunnen we gebruiken Mockito om een ​​mock te maken:

@Test openbare leegte gegevenNonAbstractMethod_whenMockitoMock_testCorrectBehaviour () {AbstractIndependent absCls = Mockito.mock (AbstractIndependent.class, Mockito.CALLS_REAL_METHODS); assertEquals ("DEFAULT-1", absCls.defaultImpl ()); }

Het belangrijkste onderdeel hier is de voorbereiding van de mock om de echte code te gebruiken wanneer een methode wordt aangeroepen gebruik makend van Mockito.CALLS_REAL_METHODS.

4. Abstracte methode genoemd vanuit niet-abstracte methode

In dit geval definieert de niet-abstracte methode de globale uitvoeringsstroom, terwijl de abstracte methode op verschillende manieren kan worden geschreven, afhankelijk van het gebruiksscenario:

openbare abstracte klasse AbstractMethodCalling {openbare abstracte String abstractFunc (); public String defaultImpl () {String res = abstractFunc (); return (res == null)? "Default": (res + "Default"); }}

Om deze code te testen, kunnen we dezelfde twee benaderingen gebruiken als voorheen: maak een concrete klasse of gebruik Mockito om een ​​mock-up te maken:

@Test openbare leegte gegevenDefaultImpl_whenMockAbstractFunc_thenExpectedBehaviour () {AbstractMethodCalling cls = Mockito.mock (AbstractMethodCalling.class); Mockito.when (cls.abstractFunc ()) .thenReturn ("Abstract"); Mockito.doCallRealMethod () .when (cls) .defaultImpl (); assertEquals ("Abstract Default", cls.defaultImpl ()); }

Hier de abstractFunc () is gestempeld met de retourwaarde die we prefereren voor de test. Dit betekent dat wanneer we de niet-abstracte methode aanroepen defaultImpl (), zal het deze stomp gebruiken.

5. Niet-abstracte methode met testbelemmering

In sommige scenario's roept de methode die we willen testen een privémethode op die een testobstructie bevat.

We moeten de belemmerende testmethode omzeilen voordat we de doelmethode testen:

openbare abstracte klasse AbstractPrivateMethods {openbare abstracte int abstractFunc (); public String defaultImpl () {return getCurrentDateTime () + "DEFAULT-1"; } private String getCurrentDateTime () {return LocalDateTime.now (). toString (); }}

In dit voorbeeld is de defaultImpl () method roept de private methode aan getCurrentDateTime (). Deze privémethode krijgt de huidige tijd tijdens runtime, wat moet worden vermeden in onze unit-tests.

Om het standaardgedrag van deze privémethode te bespotten, kunnen we niet eens gebruiken Mockito omdat het geen controle heeft over privémethoden.

In plaats daarvan moeten we PowerMock (nHoud er rekening mee dat dit voorbeeld alleen werkt met JUnit 4 omdat ondersteuning voor deze afhankelijkheid niet beschikbaar is voor JUnit 5):

@RunWith (PowerMockRunner.class) @PrepareForTest (AbstractPrivateMethods.class) openbare klasse AbstractPrivateMethodsUnitTest {@Test openbare leegte whenMockPrivateMethod_thenVerifyBehaviour () {AbstractPrivateMethods mockClass = PowerMockivate.mockClass = PowerMockivate.mockClass = PowerMockivate.mockClass = PowerMockito.doCallRealMethod () .when (mockClass) .defaultImpl (); String dateTime = LocalDateTime.now (). ToString (); PowerMockito.doReturn (dateTime) .when (mockClass, "getCurrentDateTime"); String actual = mockClass.defaultImpl (); assertEquals (dateTime + "DEFAULT-1", actueel); }}

Belangrijke bits in dit voorbeeld:

  • @Rennen met definieert PowerMock als de hardloper voor de test
  • @PrepareForTest (klasse) vertelt PowerMock om de klasse voor te bereiden voor latere verwerking

Interessant is dat we het vragen PowerMock om de privémethode te stubben getCurrentDateTime (). PowerMock zal reflectie gebruiken om het te vinden, omdat het niet van buitenaf toegankelijk is.

Zo, als we bellen defaultImpl (), wordt de stub die voor een privémethode is gemaakt, aangeroepen in plaats van de feitelijke methode.

6. Niet-abstracte methode die toegang geeft tot instantievelden

Abstracte klassen kunnen een interne status hebben die is geïmplementeerd met klassevelden. De waarde van de velden kan een significant effect hebben op de methode die wordt getest.

Als een veld openbaar of afgeschermd is, kunnen we er gemakkelijk toegang toe krijgen via de testmethode.

Maar als het privé is, moeten we gebruiken PowerMockito:

openbare abstracte klasse AbstractInstanceFields {protected int count; private boolean active = false; openbaar abstract int abstractFunc (); public String testFunc () {if (count> 5) {return "Overflow"; } actief terugkeren? "Toegevoegd": "Geblokkeerd"; }}

Hier de testFunc () methode maakt gebruik van velden op instantieniveau tellen en actief voordat het terugkeert.

Bij het testen testFunc (), kunnen we de waarde van de tellen veld door toegang te krijgen tot het exemplaar dat is gemaakt met Mockito.

Aan de andere kant, om het gedrag bij de privé te testen actief veld, we zullen opnieuw moeten gebruiken PowerMockito, en zijn Witte doos klasse:

@Test openbare leegte whenPowerMockitoAndActiveFieldTrue_thenCorrectBehaviour () {AbstractInstanceFields instClass = PowerMockito.mock (AbstractInstanceFields.class); PowerMockito.doCallRealMethod () .when (instClass) .testFunc (); Whitebox.setInternalState (instClass, "active", true); assertEquals ("Toegevoegd", instClass.testFunc ()); }

We maken een stubklasse met behulp van PowerMockito.mock (), en we gebruiken Witte doos class om de interne status van het object te controleren.

De waarde van de actief veld is gewijzigd in waar.

7. Conclusie

In deze zelfstudie hebben we meerdere voorbeelden gezien die veel gebruiksscenario's dekken. We kunnen abstracte klassen in veel meer scenario's gebruiken, afhankelijk van het gevolgde ontwerp.

Ook is het schrijven van eenheidstests voor abstracte klassemethoden net zo belangrijk als voor normale klassen en methoden. We kunnen ze allemaal testen met behulp van verschillende technieken of met verschillende beschikbare testondersteunende bibliotheken.

De volledige broncode is beschikbaar op GitHub.


$config[zx-auto] not found$config[zx-overlay] not found