Изпълняване на JUnit тестове паралелно с Maven

1. Въведение

Въпреки че изпълнението на тестове серийно работи добре през повечето време, може да искаме да ги паралелизираме, за да ускорим нещата.

В този урок ще разгледаме как да паралелизираме тестовете с помощта на JUnit и приставката Surefire на Maven. Първо ще стартираме всички тестове в един JVM процес, след това ще го опитаме с многомодулен проект.

2. Зависимости на Maven

Нека започнем с импортиране на необходимите зависимости. Ще трябва да използваме JUnit 4.7 или по-нова версия заедно със Surefire 2.16 или по-нова версия:

 junit junit 4.12 test 
 org.apache.maven.plugins maven-surefire-plugin 2.22.0 

Накратко, Surefire предоставя два начина за паралелно изпълнение на тестове:

  • Многопоточност в рамките на един JVM процес
  • Разклоняване на множество JVM процеси

3. Изпълнение на паралелни тестове

За да стартираме тест паралелно, трябва да използваме тестов бегач, който разширява org.junit.runners.ParentRunner .

Въпреки това, дори тестовете, които не декларират изрично тестово изпълнение, работят, тъй като бегачът по подразбиране разширява този клас.

След това, за да демонстрираме паралелно изпълнение на теста, ще използваме тестов пакет с два тестови класа, всеки от които има няколко метода. Всъщност всяко стандартно внедряване на тестов пакет JUnit би свършило работа.

3.1. Използване на паралелен параметър

Първо, нека активираме паралелно поведение в Surefire, използвайки паралелния параметър. Той посочва нивото на детайлност, на което бихме искали да приложим паралелизъм.

Възможните стойности са:

  • methods - изпълнява тестови методи в отделни нишки
  • класове - изпълнява тестови класове в отделни нишки
  • classesAndMethods - изпълнява класове и методи в отделни нишки
  • апартаменти - паралелно изпълнява апартаменти
  • suitesAndClasses - изпълнява суити и класове в отделни нишки
  • suitesAndMethods - създава отделни нишки за класове и за методи
  • всичко - изпълнява суити, класове, както и методи в отделни нишки

В нашия пример използваме всички :

 all 

Второ, нека дефинираме общия брой нишки, които искаме да създаде Surefire. Можем да направим това по два начина:

Използване на threadCount, което определя максималния брой нишки, които Surefire ще създаде:

10

Или с помощта на параметъра useUnlimitedThreads , където се създава по една нишка на ядро ​​на процесора:

true

По подразбиране threadCount е за ядро ​​на процесора. Можем да използваме параметъра perCoreThreadCount, за да активираме или деактивираме това поведение:

true

3.2. Използване на ограничения за броя на нишките

Сега, да кажем, че искаме да дефинираме броя нишки, които да създадем на ниво метод, клас и пакет. Можем да направим това с параметрите threadCountMethods , threadCountClasses и threadCountSuites .

Нека комбинираме тези параметри с threadCount от предишната конфигурация:

2 2 6

Тъй като ние използвахме всички в паралел, ние сме определено броят на конци за методи, апартаменти, както и класове. Не е задължително обаче да се дефинира параметърът на листа. Surefire извежда броя нишки, които да се използват в случай, че параметрите на листата са пропуснати.

Например, ако threadCountMethods е пропуснат, тогава просто трябва да се уверим, че threadCount > threadCountClasses + threadCountSuites.

Понякога може да искаме да ограничим броя на нишките, създадени за класове, суити или методи, дори когато използваме неограничен брой нишки.

Можем да приложим ограничения на броя нишки и в такива случаи:

true 2

3.3. Задаване на таймаути

Понякога може да се наложи да гарантираме, че изпълнението на теста е ограничено във времето.

За целта можем да използваме параметъра паралеленTestTimeoutForcedInSeconds . Това ще прекъсне текущите текущи нишки и няма да изпълни нито една от поставените на опашка нишки след изтичане на времето за изчакване:

5

Друга възможност е да се използва паралелноTestTimeoutInSeconds .

В този случай само нишките на опашка ще бъдат спрени от изпълнение:

3.5

Въпреки това и при двете опции тестовете ще завършат със съобщение за грешка, когато изтече времето за изчакване.

3.4. Предупреждения

Surefire извиква статични методи, анотирани с @Parameters , @BeforeClass и @AfterClass в родителската нишка. По този начин не забравяйте да проверите за потенциални несъответствия в паметта или състезателни условия, преди паралелно да провеждате тестове.

Също така тестовете, които мутират споделено състояние, определено не са добри кандидати за паралелно провеждане.

4. Изпълнение на теста в многомодулни проекти на Maven

Досега се фокусирахме върху паралелно провеждане на тестове в рамките на модул Maven.

Но да кажем, че имаме множество модули в проект на Maven. Тъй като тези модули се изграждат последователно, тестовете за всеки модул също се изпълняват последователно.

Можем да променим това поведение по подразбиране, като използваме параметъра -T на Maven, който изгражда паралелно модули . Това може да стане по два начина.

Можем да посочим точния брой нишки, които да използваме при изграждането на проекта:

mvn -T 4 surefire:test

Или използвайте преносимата версия и посочете броя нишки, които да се създават за ядро ​​на процесора:

mvn -T 1C surefire:test

Така или иначе, можем да ускорим тестовете, както и да изградим време за изпълнение.

5. Разклоняване на JVM

При паралелното изпълнение на теста чрез паралелната опция едновременността се случва в процеса на JVM с помощта на нишки .

Тъй като нишките споделят едно и също пространство в паметта, това може да бъде ефективно по отношение на паметта и скоростта. Възможно е обаче да срещнем неочаквани състезателни условия или други фини неуспехи, свързани с едновременността на теста. Както се оказва, споделянето на едно и също пространство в паметта може да бъде както благословия, така и проклятие.

За да предотврати проблеми с паралелността на ниво нишка, Surefire предоставя друг паралелен режим на изпълнение на теста: разклоняване и паралелност на ниво процес . Идеята за раздвоени процеси всъщност е доста проста. Вместо да ражда множество нишки и да разпределя тестовите методи между тях, surefire създава нови процеси и прави същото разпределение.

Тъй като няма споделена памет между различните процеси, ние няма да страдаме от тези фини грешки в едновременността. Разбира се, това става за сметка на повече използване на паметта и малко по-малка скорост.

Както и да е, за да активираме разклоняването, трябва просто да използваме свойството forkCount и да го зададем на всяка положителна стойност:

3

Тук surefire ще създаде най-много три вилици от JVM и ще стартира тестовете в тях. Стойността по подразбиране за forkCount е една, което означава, че maven-surefire-plugin създава един нов JVM процес за изпълнение на всички тестове в един модул Maven.

В forkCount имота поддържа същия синтаксис като . Тоест, ако добавим C към стойността, тази стойност ще бъде умножена с броя на наличните CPU ядра в нашата система. Например:

2.5C

След това в двуядрена машина Surefire може да създаде най-много пет вилици за паралелно изпълнение на теста.

По подразбиране Surefire ще използва отново създадените вилици за други тестове . Ако обаче зададем свойството reuseForks на false , то ще унищожи всяка вилица след стартиране на един тестов клас.

Също така, за да деактивираме разклоняването, можем да зададем forkCount на нула.

6. Заключение

За да обобщим, започнахме, като активирахме многонишково поведение и определихме степента на паралелизъм, използвайки паралелния параметър. Впоследствие приложихме ограничения за броя нишки, които Surefire трябва да създаде. По-късно задаваме параметри за изчакване, за да контролираме времето за изпълнение на теста.

Накрая разгледахме как можем да намалим времето за изпълнение на компилацията и следователно тестваме времето за изпълнение в многомодулни проекти на Maven.

Както винаги, представеният тук код е достъпен в GitHub.