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

Parallel testudførelse for JUnit 5

1. Introduktion

I denne artikel vil vi dække, hvordan man udfører parallelle enhedstests ved hjælp af JUnit 5. Først vil vi dække grundlæggende konfiguration og minimale krav for at begynde at bruge denne funktion. Dernæst viser vi kodeeksempler for forskellige situationer, og til sidst vil vi tale om synkronisering af delte ressourcer.

Parallel testeksekvering er en eksperimentel funktion, der er tilgængelig som en opt-in siden version 5.3.

2. Konfiguration

Først skal vi oprette en junit-platform.properties fil i vores src/test/resources mappe for at aktivere parallel testudførelse . Vi aktiverer paralleliseringsfunktionen ved at tilføje følgende linje i den nævnte fil:

junit.jupiter.execution.parallel.enabled = true

Lad os tjekke vores konfiguration ved at køre et par test. Først opretter vi FirstParallelUnitTest klasse og to prøver i den:

public class FirstParallelUnitTest{

    @Test
    public void first() throws Exception{
        System.out.println("FirstParallelUnitTest first() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("FirstParallelUnitTest first() end => " + Thread.currentThread().getName());
    }

    @Test
    public void second() throws Exception{
        System.out.println("FirstParallelUnitTest second() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("FirstParallelUnitTest second() end => " + Thread.currentThread().getName());
    }
}

Når vi kører vores test, får vi følgende output i konsollen:

FirstParallelUnitTest second() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest second() end => ForkJoinPool-1-worker-19
FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19

I dette output kan vi bemærke to ting. Først kører vores test sekventielt. For det andet bruger vi ForkJoin-trådpuljen. Ved at aktivere parallel udførelse begynder JUnit-motoren at bruge ForkJoin-trådpuljen.

Dernæst skal vi tilføje en konfiguration for at bruge denne trådpulje. Vi skal vælge en paralleliseringsstrategi. JUnit har to implementeringer (dynamisk og rettet ) og en brugerdefineret mulighed for at oprette vores implementering.

Dynamisk strategi bestemmer antallet af tråde  baseret på antallet af processorer/kerner ganget med faktorparameter (standard til 1) angivet ved hjælp af:

junit.jupiter.execution.parallel.config.dynamic.factor

På den anden side er den faste strategi afhængig af et foruddefineret antal tråde specificeret af:

junit.jupiter.execution.parallel.config.fixed.parallelism

For at bruge den tilpassede strategi skal vi først oprette den ved at implementere ParallelExecutionConfigurationStrategy grænseflade.

3. Test parallellisering inden for en klasse

Vi har allerede aktiveret parallel eksekvering og valgt en strategi. Nu er det tid til at udføre tests parallelt inden for samme klasse. Der er to måder at konfigurere dette på. Den ene bruger @Execution(ExecutionMode.CONCURRENT) annotation, og den anden bruger egenskabsfil og linje:

junit.jupiter.execution.parallel.mode.default = concurrent

Efter at vi har valgt, hvordan dette skal konfigureres og køre vores FirstParallelUnitTest klasse, kan vi se følgende output:

FirstParallelUnitTest second() start => ForkJoinPool-1-worker-5
FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest second() end => ForkJoinPool-1-worker-5
FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19

Fra outputtet kan vi se, at begge test starter samtidigt og i to forskellige tråde. Bemærk, at output kan ændre sig fra en kørsel til en anden. Dette forventes, når du bruger ForkJoin-trådpuljen.

Der er også en mulighed for at køre alle test i FirstParallelUnitTest klasse i samme tråd. I det nuværende omfang er det ikke muligt at bruge parallelitet og den samme trådmulighed, så lad os udvide vores omfang og tilføje en testklasse mere i næste afsnit.

4. Test parallellisering i et modul

Før vi introducerer en ny ejendom, opretter vi SecondParallelUnitTest klasse, der har to metoder, der ligner FirstParallelUnitTest:

public class SecondParallelUnitTest{

    @Test
    public void first() throws Exception{
        System.out.println("SecondParallelUnitTest first() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("SecondParallelUnitTest first() end => " + Thread.currentThread().getName());
    }

    @Test
    public void second() throws Exception{
        System.out.println("SecondParallelUnitTest second() start => " + Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println("SecondParallelUnitTest second() end => " + Thread.currentThread().getName());
    }
}

Før vi kører vores tests i samme batch, skal vi indstille egenskaben:

junit.jupiter.execution.parallel.mode.classes.default = concurrent

Når vi kører begge testklasser, får vi følgende output:

SecondParallelUnitTest second() start => ForkJoinPool-1-worker-23
FirstParallelUnitTest first() start => ForkJoinPool-1-worker-19
FirstParallelUnitTest second() start => ForkJoinPool-1-worker-9
SecondParallelUnitTest first() start => ForkJoinPool-1-worker-5
FirstParallelUnitTest first() end => ForkJoinPool-1-worker-19
SecondParallelUnitTest first() end => ForkJoinPool-1-worker-5
FirstParallelUnitTest second() end => ForkJoinPool-1-worker-9
SecondParallelUnitTest second() end => ForkJoinPool-1-worker-23

Fra outputtet kan vi se, at alle fire test kører parallelt i forskellige tråde.

Ved at kombinere to egenskaber, vi nævnte i dette og forrige afsnit, og deres værdier (samme_tråd og samtidig ), får vi fire forskellige udførelsesmåder:

  1. (samme_tråd, samme_tråd ) – alle test kører sekventielt
  2. (samme_tråd, samtidig ) – test fra én klasse kører sekventielt, men flere klasser kører parallelt
  3. (samtidig, samme_tråd ) – test fra én klasse kører parallelt, men hver klasse kører separat
  4. (samtidig, samtidig ) – test kører parallelt

5. Synkronisering

I ideelle situationer er alle vores enhedstests uafhængige og isolerede. Men nogle gange er det svært at implementere, fordi de er afhængige af delte ressourcer. Når vi derefter kører test parallelt, skal vi synkronisere over fælles ressourcer i vores test. JUnit5 giver os sådanne mekanismer i form af @ResourceLock anmærkning.

På samme måde, som før, lad os oprette ParallelResourceLockUnitTest klasse:

public class ParallelResourceLockUnitTest{
    private List<String> resources;
    @BeforeEach
    void before() {
        resources = new ArrayList<>();
        resources.add("test");
    }
    @AfterEach
    void after() {
        resources.clear();
    }
    @Test
    @ResourceLock(value = "resources")
    public void first() throws Exception {
        System.out.println("ParallelResourceLockUnitTest first() start => " + Thread.currentThread().getName());
        resources.add("first");
        System.out.println(resources);
        Thread.sleep(500);
        System.out.println("ParallelResourceLockUnitTest first() end => " + Thread.currentThread().getName());
    }
    @Test
    @ResourceLock(value = "resources")
    public void second() throws Exception {
        System.out.println("ParallelResourceLockUnitTest second() start => " + Thread.currentThread().getName());
        resources.add("second");
        System.out.println(resources);
        Thread.sleep(500);
        System.out.println("ParallelResourceLockUnitTest second() end => " + Thread.currentThread().getName());
    }
}

@ResourceLock giver os mulighed for at angive, hvilken ressource der deles, og hvilken type lås vi vil bruge (standard er ResourceAccessMode.READ_WRITE ) . Med den nuværende opsætning vil JUnit-motoren registrere, at vores tests både bruger en delt ressource og vil udføre dem sekventielt:

ParallelResourceLockUnitTest second() start => ForkJoinPool-1-worker-5
[test, second]
ParallelResourceLockUnitTest second() end => ForkJoinPool-1-worker-5
ParallelResourceLockUnitTest first() start => ForkJoinPool-1-worker-19
[test, first]
ParallelResourceLockUnitTest first() end => ForkJoinPool-1-worker-19

6. Konklusion

I denne artikel dækkede vi først, hvordan man konfigurerer parallel udførelse. Dernæst, hvad er tilgængelige strategier for parallelisme, og hvordan konfigureres et antal tråde? Derefter dækkede vi, hvordan forskellige konfigurationer påvirker testudførelsen. Til sidst dækkede vi synkroniseringen af ​​delte ressourcer.

Som altid kan koden fra denne artikel findes på GitHub.


Java tag