Test af en abstrakt klasse med JUnit

1. Oversigt

I denne vejledning analyserer vi forskellige brugssager og mulige alternative løsninger til enhedstest af abstrakte klasser med ikke-abstrakte metoder.

Noter det test af abstrakte klasser skal næsten altid gennemgå den offentlige API for de konkrete implementeringer, så anvend ikke nedenstående teknikker, medmindre du er sikker på, hvad du laver.

2. Maven-afhængigheder

Lad os starte med Maven-afhængigheder:

 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 

Du kan finde de nyeste versioner af disse biblioteker på Maven Central.

Powermock understøttes ikke fuldt ud til Junit5. Også, powermock-module-junit4 bruges kun til et eksempel præsenteret i afsnit 5.

3. Uafhængig ikke-abstrakt metode

Lad os overveje et tilfælde, når vi har en abstrakt klasse med en offentlig ikke-abstrakt metode:

offentlig abstrakt klasse AbstractIndependent {offentlig abstrakt int abstraktFunc (); public String defaultImpl () {return "DEFAULT-1"; }}

Vi vil teste metoden defaultImpl (), og vi har to mulige løsninger - ved hjælp af en konkret klasse eller ved hjælp af Mockito.

3.1. Brug af en betonklasse

Opret en konkret klasse, der strækker sig Abstrakt uafhængig klasse, og brug den til at teste metoden:

offentlig klasse ConcreteImpl udvider AbstractIndependent {@Override public int abstractFunc () {return 4; }}
@ Test offentlig ugyldighed givenNonAbstractMethod_whenConcreteImpl_testCorrectBehaviour () {ConcreteImpl conClass = new ConcreteImpl (); Streng faktisk = conClass.defaultImpl (); assertEquals ("DEFAULT-1", faktisk); }

Ulempen ved denne løsning er behovet for at skabe den konkrete klasse med dummy implementeringer af alle abstrakte metoder.

3.2. Brug af Mockito

Alternativt kan vi bruge Mockito at skabe en mock:

@Test offentlig ugyldighed givenNonAbstractMethod_whenMockitoMock_testCorrectBehaviour () {AbstractIndependent absCls = Mockito.mock (AbstractIndependent.class, Mockito.CALLS_REAL_METHODS); assertEquals ("DEFAULT-1", absCls.defaultImpl ()); }

Den vigtigste del her er forberedelse af mock til at bruge den rigtige kode, når en metode påberåbes ved brug af Mockito.CALLS_REAL_METHODS.

4. Abstrakt metode kaldet fra ikke-abstrakt metode

I dette tilfælde definerer den ikke-abstrakte metode den globale eksekveringsstrøm, mens den abstrakte metode kan skrives på forskellige måder afhængigt af brugssagen:

offentlig abstrakt klasse AbstractMethodCalling {offentlig abstrakt String abstractFunc (); public String defaultImpl () {String res = abstractFunc (); returnere (res == null)? "Standard": (res + "Standard"); }}

For at teste denne kode kan vi bruge de samme to tilgange som før - enten oprette en konkret klasse eller bruge Mockito til at oprette en mock:

@Test offentlig ugyldighed givenDefaultImpl_whenMockAbstractFunc_thenExpectedBehaviour () {AbstractMethodCalling cls = Mockito.mock (AbstractMethodCalling.class); Mockito.when (cls.abstractFunc ()) .thenReturn ("Abstract"); Mockito.doCallRealMethod () .when (cls) .defaultImpl (); assertEquals ("Abstrakt standard", cls.defaultImpl ()); }

Her, den abstractFunc () stubbes med den returværdi, vi foretrækker til testen. Dette betyder, at når vi kalder den ikke-abstrakte metode defaultImpl (), det bruger denne stub.

5. Ikke-abstrakt metode med testhindring

I nogle scenarier kalder den metode, vi vil teste, en privat metode, der indeholder en testhindring.

Vi er nødt til at omgå den hindrende testmetode, før vi tester målmetoden:

offentlig abstrakt klasse AbstractPrivateMethods {offentlig abstrakt int abstraktFunc (); public String defaultImpl () {return getCurrentDateTime () + "DEFAULT-1"; } privat streng getCurrentDateTime () {return LocalDateTime.now (). toString (); }}

I dette eksempel er defaultImpl () metode kalder den private metode getCurrentDateTime (). Denne private metode får den aktuelle tid ved kørsel, hvilket bør undgås i vores enhedstest.

Nu, for at spotte standardopførelsen af ​​denne private metode, kan vi ikke engang bruge Mockito fordi det ikke kan kontrollere private metoder.

I stedet skal vi bruge PowerMock (nBemærk, at dette eksempel kun fungerer med JUnit 4, fordi understøttelse af denne afhængighed ikke er tilgængelig for JUnit 5):

@RunWith (PowerMockRunner.class) @PrepareForTest (AbstractPrivateMethods.class) offentlig klasse AbstractPrivateMethodsUnitTest {@Test offentlig ugyldig nårMockPrivateMethod_thenVerifyBehaviour () {AbstractPrivateMethods mockClass = PowerMockito.mock; PowerMockito.doCallRealMethod () .when (mockClass) .defaultImpl (); String dateTime = LocalDateTime.now (). ToString (); PowerMockito.doReturn (dateTime) .when (mockClass, "getCurrentDateTime"); Streng faktisk = mockClass.defaultImpl (); assertEquals (dateTime + "DEFAULT-1", faktisk); }}

Vigtige bits i dette eksempel:

  • @RunWith definerer PowerMock som løber til testen
  • @PrepareForTest (klasse) fortæller PowerMock at forberede klassen til senere behandling

Interessant nok spørger vi PowerMock for at stoppe den private metode getCurrentDateTime (). PowerMock bruger refleksion til at finde det, fordi det ikke er tilgængeligt udefra.

, når vi ringer defaultImpl (), den stub, der er oprettet til en privat metode, påkaldes i stedet for den aktuelle metode.

6. Ikke-abstrakt metode, der får adgang til instansfelter

Abstrakte klasser kan have en intern tilstand implementeret med klassefelter. Værdien af ​​felterne kunne have en væsentlig effekt på metoden, der blev testet.

Hvis et felt er offentligt eller beskyttet, kan vi let få adgang til det fra testmetoden.

Men hvis det er privat, skal vi bruge det PowerMockito:

offentlig abstrakt klasse AbstractInstanceFields {beskyttet int count; privat boolsk aktiv = falsk; offentlig abstrakt int abstraktFunc (); public String testFunc () {if (count> 5) {return "Overflow"; } vende tilbage aktivt? "Tilføjet": "Blokeret"; }}

Her, den testFunc () metoden bruger felter på instansniveau tælle og aktiv inden den vender tilbage.

Ved test testFunc (), kan vi ændre værdien af tælle felt ved at få adgang til instans oprettet ved hjælp af Mockito.

På den anden side for at teste adfærd med det private aktiv felt, bliver vi igen nødt til at bruge PowerMockito, ogdet er Whitebox klasse:

@Test offentlig ugyldig nårPowerMockitoAndActiveFieldTrue_thenCorrectBehaviour () {AbstractInstanceFields instClass = PowerMockito.mock (AbstractInstanceFields.class); PowerMockito.doCallRealMethod (). Når (instClass) .testFunc (); Whitebox.setInternalState (instClass, "aktiv", sand); assertEquals ("Tilføjet", instClass.testFunc ()); }

Vi opretter en stubklasse ved hjælp af PowerMockito.mock (), og vi bruger Whitebox klasse til at kontrollere objektets interne tilstand.

Værdien af aktiv felt ændres til rigtigt.

7. Konklusion

I denne vejledning har vi set flere eksempler, der dækker mange brugssager. Vi kan bruge abstrakte klasser i mange flere scenarier afhængigt af det design, der følges.

Også at skrive enhedstest for abstrakte klassemetoder er lige så vigtig som for normale klasser og metoder. Vi kan teste hver af dem ved hjælp af forskellige teknikker eller forskellige tilgængelige testbiblioteker.

Den fulde kildekode er tilgængelig på GitHub.