Menjalankan Tes JUnit secara Paralel dengan Maven

1. Perkenalan

Meskipun menjalankan pengujian secara serial berfungsi dengan baik di sebagian besar waktu, kami mungkin ingin memparalelkannya untuk mempercepat.

Dalam tutorial ini, kami akan membahas cara memparalelkan pengujian menggunakan JUnit dan Plugin Surefire milik Maven. Pertama, kami akan menjalankan semua pengujian dalam satu proses JVM, lalu kami akan mencobanya dengan proyek multi-modul.

2. Ketergantungan Maven

Mari kita mulai dengan mengimpor dependensi yang diperlukan. Kita harus menggunakan JUnit 4.7 atau yang lebih baru bersama dengan Surefire 2.16 atau yang lebih baru:

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

Singkatnya, Surefire menyediakan dua cara untuk menjalankan tes secara paralel:

  • Multithreading di dalam proses JVM tunggal
  • Membagi beberapa proses JVM

3. Menjalankan Tes Paralel

Untuk menjalankan pengujian secara paralel kita harus menggunakan runner pengujian yang memperluas org.junit.runners.ParentRunner .

Namun, bahkan pengujian yang tidak mendeklarasikan runner pengujian eksplisit berfungsi, karena runner default memperluas kelas ini.

Selanjutnya, untuk mendemonstrasikan eksekusi pengujian paralel, kita akan menggunakan rangkaian pengujian dengan dua kelas pengujian yang masing-masing memiliki beberapa metode. Faktanya, implementasi standar rangkaian pengujian JUnit bisa dilakukan.

3.1. Menggunakan Parameter Paralel

Pertama, mari aktifkan perilaku paralel di Surefire menggunakan parameter paralel . Ini menyatakan tingkat perincian di mana kami ingin menerapkan paralelisme.

Nilai yang mungkin adalah:

  • metode - menjalankan metode pengujian di utas terpisah
  • kelas - menjalankan kelas uji di utas terpisah
  • classAndMethods - menjalankan class dan metode di thread terpisah
  • suite - menjalankan suite secara paralel
  • suitesAndClasses - menjalankan suite dan kelas di utas terpisah
  • suitesAndMethods - membuat utas terpisah untuk kelas dan untuk metode
  • all - menjalankan suite, kelas, serta metode di utas terpisah

Dalam contoh kami, kami menggunakan semua :

 all 

Kedua, mari kita tentukan jumlah total utas yang kita ingin buat Surefire. Kita dapat melakukannya dengan dua cara:

Menggunakan threadCount yang menentukan jumlah maksimum utas Surefire akan membuat:

10

Atau menggunakan parameter useUnlimitedThreads di mana satu utas dibuat per inti CPU:

true

Secara default, threadCount adalah per inti CPU. Kita dapat menggunakan parameter perCoreThreadCount untuk mengaktifkan atau menonaktifkan perilaku ini:

true

3.2. Menggunakan Batasan Jumlah Benang

Sekarang, katakanlah kita ingin menentukan jumlah utas yang akan dibuat pada tingkat metode, kelas, dan suite. Kita bisa melakukan ini dengan parameter threadCountMethods , threadCountClasses , dan threadCountSuites .

Mari gabungkan parameter ini dengan threadCount dari konfigurasi sebelumnya:

2 2 6

Karena kami menggunakan semuanya secara paralel, kami telah menentukan jumlah utas untuk metode, suite, dan kelas. Namun, tidak wajib untuk menentukan parameter daun. Surefire menyimpulkan jumlah utas yang akan digunakan jika parameter daun dihilangkan.

Misalnya, jika threadCountMethods dihilangkan, kita hanya perlu memastikan threadCount > threadCountClasses + threadCountSuites.

Terkadang kami mungkin ingin membatasi jumlah utas yang dibuat untuk kelas atau suite atau metode bahkan saat kami menggunakan jumlah utas yang tidak terbatas.

Kami juga dapat menerapkan batasan jumlah utas dalam kasus seperti ini:

true 2

3.3. Mengatur Batas Waktu

Terkadang kami mungkin perlu memastikan bahwa eksekusi uji dibatasi waktu.

Untuk melakukan itu kita dapat menggunakan parameter parallelTestTimeoutForcedInSeconds . Ini akan mengganggu thread yang sedang berjalan dan tidak akan mengeksekusi thread apa pun yang antri setelah waktu tunggu berlalu:

5

Pilihan lainnya adalah menggunakan parallelTestTimeoutInSeconds .

Dalam kasus ini, hanya thread yang antri yang akan dihentikan dari eksekusi:

3.5

Namun demikian, dengan kedua opsi tersebut, pengujian akan diakhiri dengan pesan kesalahan saat batas waktu telah berlalu.

3.4. Peringatan

Surefire memanggil metode statis yang dianotasi dengan @Parameters , @BeforeClass , dan @AfterClass di thread induk. Karenanya, pastikan untuk memeriksa kemungkinan ketidakkonsistenan memori atau kondisi balapan sebelum menjalankan pengujian secara paralel.

Selain itu, pengujian yang mengubah status bersama jelas bukan kandidat yang baik untuk dijalankan secara paralel.

4. Eksekusi Uji dalam Proyek Multi-Modul Maven

Hingga saat ini, kami fokus menjalankan pengujian secara paralel di dalam modul Maven.

Tetapi katakanlah kita memiliki banyak modul dalam proyek Maven. Karena modul ini dibuat secara berurutan, pengujian untuk setiap modul juga dijalankan secara berurutan.

Kita bisa mengubah perilaku default ini dengan menggunakan parameter -T Maven yang membangun modul secara paralel . Ini bisa dilakukan dengan dua cara.

Kita dapat menentukan jumlah pasti utas yang akan digunakan saat membangun proyek:

mvn -T 4 surefire:test

Atau gunakan versi portabel dan tentukan jumlah utas yang akan dibuat per inti CPU:

mvn -T 1C surefire:test

Apa pun itu, kami dapat mempercepat pengujian serta waktu eksekusi build.

5. Membagi JVM

Dengan eksekusi uji paralel melalui opsi paralel , konkurensi terjadi di dalam proses JVM menggunakan utas .

Karena utas berbagi ruang memori yang sama, ini dapat menjadi efisien dalam hal memori dan kecepatan. Namun, kami mungkin mengalami kondisi balapan yang tidak terduga atau kegagalan pengujian halus terkait konkurensi lainnya. Ternyata, berbagi ruang memori yang sama bisa menjadi berkah sekaligus kutukan.

Untuk mencegah masalah konkurensi tingkat utas, Surefire menyediakan mode eksekusi uji paralel lain: percabangan dan konkurensi tingkat proses . Ide proses bercabang sebenarnya cukup sederhana. Alih-alih menelurkan banyak utas dan mendistribusikan metode pengujian di antara mereka, pasti menciptakan proses baru dan melakukan distribusi yang sama.

Karena tidak ada memori bersama di antara proses yang berbeda, kami tidak akan mengalami bug konkurensi halus tersebut. Tentu saja, ini datang dengan mengorbankan lebih banyak penggunaan memori dan kecepatan yang lebih sedikit.

Bagaimanapun, untuk mengaktifkan forking, kita hanya perlu menggunakan properti forkCount dan menyetelnya ke nilai positif apa pun:

3

Di sini, pasti akan membuat paling banyak tiga garpu dari JVM dan menjalankan tes di dalamnya. Nilai default forkCount adalah satu, yang berarti bahwa maven-surefire-plugin membuat satu proses JVM baru untuk menjalankan semua pengujian dalam satu modul Maven.

The forkCount properti mendukung sintaks yang sama seperti -T . Artinya, jika kita menambahkan C ke nilai tersebut, nilai tersebut akan dikalikan dengan jumlah inti CPU yang tersedia di sistem kita. Misalnya:

2.5C

Kemudian dalam mesin dua inti, Surefire dapat membuat paling banyak lima garpu untuk eksekusi uji paralel.

Secara default, Surefire akan menggunakan kembali garpu yang dibuat untuk pengujian lainnya . Namun, jika kita menyetel properti reuseForks ke false , ini akan menghancurkan setiap fork setelah menjalankan satu kelas pengujian.

Selain itu, untuk menonaktifkan forking, kita dapat menyetel forkCount ke nol.

6. Kesimpulan

Singkatnya, kami memulai dengan mengaktifkan perilaku multi-threaded dan menentukan tingkat paralelisme menggunakan parameter paralel . Selanjutnya, kami menerapkan batasan pada jumlah utas yang harus dibuat oleh Surefire. Nanti, kami menetapkan parameter waktu tunggu untuk mengontrol waktu eksekusi pengujian.

Terakhir, kami melihat bagaimana kami dapat mengurangi waktu eksekusi build dan oleh karena itu menguji waktu eksekusi dalam project Maven multi-modul.

Seperti biasa, kode yang disajikan di sini tersedia di GitHub.