Java >> Java tutorial >  >> Tag >> JUnit

Skrive skabeloner til testcases ved hjælp af JUnit 5

1. Oversigt

JUnit 5-biblioteket byder på mange nye funktioner i forhold til sine tidligere versioner. En sådan funktion er testskabeloner. Kort sagt er testskabeloner en kraftfuld generalisering af JUnit 5's parameteriserede og gentagne tests.

I denne vejledning skal vi lære, hvordan man opretter en testskabelon ved hjælp af JUnit 5.

2. Maven Dependencies

Lad os starte med at tilføje afhængighederne til vores pom.xml .

Vi er nødt til at tilføje den primære JUnit 5  junit-jupiter-engine afhængighed:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.8.1</version>
</dependency>

Ud over dette skal vi også tilføje junit-jupiter-api afhængighed:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.1</version>
</dependency>

Ligeledes kan vi tilføje de nødvendige afhængigheder til vores build.gradle fil:

testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1'
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.8.1'

3. Problemformuleringen

Før vi ser på testskabeloner, lad os kort tage et kig på JUnit 5's parameteriserede test. Parameteriserede test giver os mulighed for at injicere forskellige parametre i testmetoden. Som et resultat, når du bruger parametriserede tests, vi kan udføre en enkelt testmetode flere gange med forskellige parametre.

Lad os antage, at vi nu gerne vil køre vores testmetode flere gange - ikke bare med forskellige parametre, men også under en anden påkaldelseskontekst hver gang.

Med andre ord, vi ønsker, at testmetoden skal udføres flere gange, hvor hver påkaldelse bruger en anden kombination af konfigurationer såsom:

  • ved at bruge forskellige parametre
  • forberedelse af testklasseforekomsten anderledes – det vil sige, at injicere forskellige afhængigheder i testforekomsten
  • køre testen under forskellige forhold, såsom aktivering/deaktivering af en undergruppe af påkaldelser, hvis miljøet er "QA "
  • kører med en anden livscyklus-tilbagekaldsadfærd – måske vil vi konfigurere og rive en database ned før og efter en undergruppe af påkaldelser

Brugen af ​​parameteriserede test viser sig hurtigt at være begrænset i dette tilfælde. Heldigvis tilbyder JUnit 5 en kraftfuld løsning til dette scenarie i form af testskabeloner.

4. Test skabeloner

Testskabeloner i sig selv er ikke testcases. I stedet er de, som deres navn antyder, blot skabeloner til givne testcases. De er en kraftfuld generalisering af parametriserede og gentagne tests.

Testskabeloner påberåbes én gang for hver påkaldelseskontekst, der leveres til dem af udbyderen af ​​påkaldelseskonteksten.

Lad os nu se på et eksempel på testskabelonerne. Som vi har fastslået ovenfor, er hovedaktørerne:

  • en testmålmetode
  • en testskabelonmetode
  • en eller flere invokationskontekstudbydere, der er registreret med skabelonmetoden
  • en eller flere påkaldelseskontekster leveret af hver udbyder af påkaldelseskontekst

4.1. Testmålmetoden

Til dette eksempel vil vi bruge en simpel UserIdGeneratorImpl.generate metode som vores testmål.

Lad os definere UserIdGeneratorImpl klasse:

public class UserIdGeneratorImpl implements UserIdGenerator {
    private boolean isFeatureEnabled;

    public UserIdGeneratorImpl(boolean isFeatureEnabled) {
        this.isFeatureEnabled = isFeatureEnabled;
    }

    public String generate(String firstName, String lastName) {
        String initialAndLastName = firstName.substring(0, 1).concat(lastName);
        return isFeatureEnabled ? "bael".concat(initialAndLastName) : initialAndLastName;
    }
}

generer metode, som er vores testmål, tager fornavn og efternavn som parametre og genererer et bruger-id. Formatet på bruger-id'et varierer, afhængigt af om en funktionskontakt er aktiveret eller ej.

Lad os se, hvordan det ser ud:

Given feature switch is disabled When firstName = "John" and lastName = "Smith" Then "JSmith" is returned
Given feature switch is enabled When firstName = "John" and lastName = "Smith" Then "baelJSmith" is returned

Lad os derefter skrive testskabelonmetoden.

4.2. Testskabelonmetoden

Her er en testskabelon til vores testmålmetode UserIdGeneratorImpl.generate :

public class UserIdGeneratorImplUnitTest {
    @TestTemplate
    @ExtendWith(UserIdGeneratorTestInvocationContextProvider.class)
    public void whenUserIdRequested_thenUserIdIsReturnedInCorrectFormat(UserIdGeneratorTestCase testCase) {
        UserIdGenerator userIdGenerator = new UserIdGeneratorImpl(testCase.isFeatureEnabled());

        String actualUserId = userIdGenerator.generate(testCase.getFirstName(), testCase.getLastName());

        assertThat(actualUserId).isEqualTo(testCase.getExpectedUserId());
    }
}

Lad os se nærmere på testskabelonmetoden.

Til at begynde med opretter vi vores testskabelonmetode ved at markere den med JUnit 5 @TestTemplate anmærkning .

Derefter registrerer vi en kontekstudbyder , UserIdGeneratorTestInvocationContextProvider, ved at bruge @ExtendWith anmærkning . Vi kan registrere flere kontekstudbydere med testskabelonen. Men med henblik på dette eksempel registrerer vi en enkelt udbyder.

Skabelonmetoden modtager også en forekomst af UserIdGeneratorTestCase som en parameter. Dette er simpelthen en indpakningsklasse for input og det forventede resultat af testcasen:

public class UserIdGeneratorTestCase {
    private boolean isFeatureEnabled;
    private String firstName;
    private String lastName;
    private String expectedUserId;

    // Standard setters and getters
}

Til sidst påberåber vi os testmålmetoden og hævder, at resultatet er som forventet

Nu er det tid til at definere vores invokationskontekstudbyder.

4.3. Invocation Context Provider

Vi skal registrere mindst én TestTemplateInvocationContextProvider med vores testskabelon. Hver registrerede TestTemplateInvocationContextProvider giver en Strøm af TestTemplateInvocationContext forekomster .

Tidligere brugte @ExtendWith annotation, har vi registreret UserIdGeneratorTestInvocationContextProvider som vores invokationsudbyder.

Lad os definere denne klasse nu:

public class UserIdGeneratorTestInvocationContextProvider implements TestTemplateInvocationContextProvider {
    //...
}

Vores påkaldelseskontekst implementerer TestTemplateInvocationContextProvider grænseflade, som har to metoder:

  • understøtterTestTemplate
  • givTestTemplateInvocationContexts

Lad os starte med at implementere supportsTestTemplate metode:

@Override
public boolean supportsTestTemplate(ExtensionContext extensionContext) {
    return true;
}

JUnit 5-udførelsesmotoren kalder supportsTestTemplate metode først for at validere, om udbyderen er anvendelig for den givne ExecutionContext . I dette tilfælde returnerer vi blot true .

Lad os nu implementere provideTestTemplateInvocationContexts metode:

@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
  ExtensionContext extensionContext) {
    boolean featureDisabled = false;
    boolean featureEnabled = true;
 
    return Stream.of(
      featureDisabledContext(
        new UserIdGeneratorTestCase(
          "Given feature switch disabled When user name is John Smith Then generated userid is JSmith",
          featureDisabled,
          "John",
          "Smith",
          "JSmith")),
      featureEnabledContext(
        new UserIdGeneratorTestCase(
          "Given feature switch enabled When user name is John Smith Then generated userid is baelJSmith",
          featureEnabled,
          "John",
          "Smith",
          "baelJSmith"))
    );
}

Formålet med provideTestTemplateInvocationContexts metode er at levere en Strøm af TestTemplateInvocationContext tilfælde. For vores eksempel returnerer det to forekomster, leveret af metoderne featureDisabledContext og featureEnabledContext . Derfor vil vores testskabelon køre to gange.

Lad os derefter se på de to TestTemplateInvocationContext forekomster returneret af disse metoder.

4.4. Invokationskontekstforekomsterne

Invokationskonteksterne er implementeringer af TestTemplateInvocationContext interface og implementer følgende metoder:

  • getDisplayName – angiv et testvisningsnavn
  • getAdditionalExtensions – returner yderligere udvidelser til invokationskonteksten

Lad os definere funktionen DisabledContext metode, der returnerer vores første invokationskontekstinstans:

private TestTemplateInvocationContext featureDisabledContext(
  UserIdGeneratorTestCase userIdGeneratorTestCase) {
    return new TestTemplateInvocationContext() {
        @Override
        public String getDisplayName(int invocationIndex) {
            return userIdGeneratorTestCase.getDisplayName();
        }

        @Override
        public List<Extension> getAdditionalExtensions() {
            return asList(
              new GenericTypedParameterResolver(userIdGeneratorTestCase), 
              new BeforeTestExecutionCallback() {
                  @Override
                  public void beforeTestExecution(ExtensionContext extensionContext) {
                      System.out.println("BeforeTestExecutionCallback:Disabled context");
                  }
              }, 
              new AfterTestExecutionCallback() {
                  @Override
                  public void afterTestExecution(ExtensionContext extensionContext) {
                      System.out.println("AfterTestExecutionCallback:Disabled context");
                  }
              }
            );
        }
    };
}

For det første for påkaldelseskonteksten returneret af featureDisabledContext metode, er de udvidelser, vi registrerer:

  • GenericTypedParameterResolver – en parameteropløsningsudvidelse
  • BeforeTestExecutionCallback – en livscyklustilbagekaldsforlængelse, der kører umiddelbart før testudførelsen
  • AfterTestExecutionCallback – en livscyklustilbagekaldsforlængelse, der kører umiddelbart efter testudførelsen

Men for den anden invokationskontekst, returneret af featureEnabledContext metode, lad os registrere et andet sæt udvidelser (behold GenericTypedParameterResolver ):

private TestTemplateInvocationContext featureEnabledContext(
  UserIdGeneratorTestCase userIdGeneratorTestCase) {
    return new TestTemplateInvocationContext() {
        @Override
        public String getDisplayName(int invocationIndex) {
            return userIdGeneratorTestCase.getDisplayName();
        }
    
        @Override
        public List<Extension> getAdditionalExtensions() {
            return asList(
              new GenericTypedParameterResolver(userIdGeneratorTestCase), 
              new DisabledOnQAEnvironmentExtension(), 
              new BeforeEachCallback() {
                  @Override
                  public void beforeEach(ExtensionContext extensionContext) {
                      System.out.println("BeforeEachCallback:Enabled context");
                  }
              }, 
              new AfterEachCallback() {
                  @Override
                  public void afterEach(ExtensionContext extensionContext) {
                      System.out.println("AfterEachCallback:Enabled context");
                  }
              }
            );
        }
    };
}

For den anden invokationskontekst er de udvidelser, som vi registrerer:

  • GenericTypedParameterResolver – en parameteropløsningsudvidelse
  • DisabledOnQAEnvironmentExtension – en eksekveringsbetingelse for at deaktivere testen, hvis miljøegenskaben (indlæst fra application.properties fil) er "qa "
  • Før hvert tilbagekald – en livscyklustilbagekaldsforlængelse, der kører før hver testmetodeudførelse
  • AfterEachCallback – en livscyklustilbagekaldsforlængelse, der kører efter hver testmetodeudførelse

Fra ovenstående eksempel er det tydeligt at se, at:

  • den samme testmetode køres under flere påkaldelseskontekster
  • hver invokationskontekst bruger sit eget sæt af udvidelser, der adskiller sig både i antal og art fra udvidelserne i andre invokationskontekster

Som et resultat kan en testmetode påkaldes flere gange under en helt anden påkaldelseskontekst hver gang. Og ved at registrere flere kontekstudbydere kan vi levere endnu flere lag af påkaldelseskontekster, som vi kan køre testen under.

5. Konklusion

I denne artikel har vi set på, hvordan JUnit 5's testskabeloner er en kraftfuld generalisering af parameteriserede og gentagne tests.

Til at begynde med så vi på nogle begrænsninger ved de parameteriserede tests. Dernæst diskuterede vi, hvordan testskabeloner overvinder begrænsningerne ved at tillade, at en test køres under en anden kontekst for hver påkaldelse.

Til sidst så vi på et eksempel på oprettelse af en ny testskabelon. Vi opdelte eksemplet for at forstå, hvordan skabeloner fungerer sammen med udbydere af invokationskontekster og invokationskontekster.

Som altid er kildekoden til eksemplerne brugt i denne artikel tilgængelig på GitHub.


Java tag