Bagaimana Anda menguji metode yang menjalankan proses asinkron dengan JUnit?
Saya tidak tahu bagaimana membuat tes saya menunggu proses berakhir (ini bukan tes unit, ini lebih seperti tes integrasi karena melibatkan beberapa kelas dan bukan hanya satu).
Jawaban:
IMHO itu praktik buruk untuk membuat unit test membuat atau menunggu utas, dll. Anda ingin tes ini berjalan dalam hitungan detik. Itu sebabnya saya ingin mengusulkan pendekatan 2 langkah untuk menguji proses async.
sumber
Alternatifnya adalah menggunakan kelas CountDownLatch .
CATATAN Anda tidak bisa hanya menggunakan sinkronisasi dengan objek biasa sebagai kunci, karena panggilan balik cepat dapat melepaskan kunci sebelum metode tunggu kunci dipanggil. Lihat ini posting blog oleh Joe Walnes.
EDIT Menghapus blok yang disinkronkan di sekitar CountDownLatch berkat komentar dari @jtahlborn dan @Ring
sumber
Anda dapat mencoba menggunakan perpustakaan Awaitility . Mudah untuk menguji sistem yang sedang Anda bicarakan.
sumber
CountDownLatch
(lihat jawaban oleh @Martin) lebih baik dalam hal ini.Jika Anda menggunakan CompletableFuture (diperkenalkan di Java 8) atau SettableFuture (dari Google Guava ), Anda dapat menyelesaikan tes segera setelah selesai, daripada menunggu waktu yang telah ditentukan sebelumnya. Tes Anda akan terlihat seperti ini:
sumber
Mulai proses dan tunggu hasilnya menggunakan a
Future
.sumber
Salah satu metode yang saya temukan sangat berguna untuk menguji metode asinkron adalah menyuntikkan
Executor
contoh dalam konstruktor objek-ke-tes. Dalam produksi, instance pelaksana dikonfigurasikan untuk berjalan secara tidak sinkron sementara dalam pengujian dapat diejek untuk dijalankan secara sinkron.Jadi misalkan saya mencoba untuk menguji metode asinkron
Foo#doAsync(Callback c)
,Dalam produksi, saya akan membangun
Foo
denganExecutors.newSingleThreadExecutor()
contoh Executor sementara dalam pengujian saya mungkin akan membangunnya dengan eksekutor sinkron yang melakukan hal berikut -Sekarang tes JUnit saya tentang metode asinkron cukup bersih -
sumber
WebClient
Tidak ada yang salah dengan pengujian kode ulir / async, terutama jika threading adalah titik dari kode yang Anda uji. Pendekatan umum untuk menguji hal ini adalah dengan:
Tapi itu banyak boilerplate untuk satu tes. Pendekatan yang lebih baik / sederhana adalah dengan hanya menggunakan ConcurrentUnit :
Manfaat dari
CountdownLatch
pendekatan ini adalah bahwa itu kurang bertele-tele karena kegagalan pernyataan yang terjadi pada setiap utas dilaporkan dengan benar ke utas utama, artinya pengujian gagal ketika seharusnya. Lelang yang membandingkanCountdownLatch
pendekatan ke ConcurrentUnit ada di sini .Saya juga menulis posting blog tentang topik untuk mereka yang ingin belajar sedikit lebih detail.
sumber
Bagaimana dengan menelepon
SomeObject.wait
dannotifyAll
seperti yang dijelaskan di sini ATAU menggunakan metode RobotiumSolo.waitForCondition(...)
ATAU menggunakan kelas yang saya tulis untuk melakukan ini (lihat komentar dan kelas uji untuk cara menggunakan)sumber
Saya menemukan socket.io perpustakaan untuk menguji logika asinkron. Ini terlihat sederhana dan cara singkat menggunakan LinkedBlockingQueue . Berikut ini contohnya :
Menggunakan LinkedBlockingQueue, ambil API untuk memblokir hingga mendapatkan hasil seperti cara sinkron. Dan atur batas waktu untuk menghindari asumsi terlalu banyak waktu untuk menunggu hasilnya.
sumber
Perlu disebutkan bahwa ada bab yang sangat berguna
Testing Concurrent Programs
dalam Concurrency in Practice yang menjelaskan beberapa pendekatan pengujian unit dan memberikan solusi untuk masalah.sumber
Inilah yang saya gunakan saat ini jika hasil tes diproduksi secara tidak sinkron.
Menggunakan impor statis, tesnya terbilang bagus. (perhatikan, dalam contoh ini saya memulai utas untuk menggambarkan ide)
Jika
f.complete
tidak dipanggil, tes akan gagal setelah batas waktu. Anda juga bisa menggunakanf.completeExceptionally
untuk gagal lebih awal.sumber
Ada banyak jawaban di sini tetapi yang sederhana adalah dengan membuat CompletableFuture yang lengkap dan menggunakannya:
Jadi dalam pengujian saya:
Saya hanya memastikan semua hal ini dipanggil. Teknik ini berfungsi jika Anda menggunakan kode ini:
Ini akan zip melalui itu karena semua CompletableFutures selesai!
sumber
Hindari pengujian dengan utas paralel kapan pun Anda bisa (yang sebagian besar waktu). Ini hanya akan membuat tes Anda terkelupas (terkadang lulus, kadang gagal).
Hanya ketika Anda perlu memanggil beberapa pustaka / sistem lain, Anda mungkin harus menunggu utas lainnya, dalam hal ini selalu gunakan pustaka Awaitility sebagai ganti
Thread.sleep()
.Jangan hanya menelepon
get()
ataujoin()
dalam pengujian Anda, kalau tidak tes Anda akan berjalan selamanya di server CI Anda jika masa depan tidak pernah selesai. Selalu tegaskanisDone()
dulu dalam tes Anda sebelum meneleponget()
. Untuk CompletionStage, yaitu.toCompletableFuture().isDone()
.Saat Anda menguji metode non-pemblokiran seperti ini:
maka Anda tidak hanya harus menguji hasilnya dengan melewati Masa Depan yang telah selesai dalam tes, Anda juga harus memastikan bahwa metode
doSomething()
Anda tidak memblokir dengan meneleponjoin()
atauget()
. Ini penting khususnya jika Anda menggunakan kerangka kerja non-blocking.Untuk melakukannya, uji dengan masa depan yang belum selesai yang Anda setel untuk diselesaikan secara manual:
Dengan begitu, jika Anda menambahkan
future.join()
doSomething (), tes akan gagal.Jika Layanan Anda menggunakan ExecutorService seperti di
thenApplyAsync(..., executorService)
, maka dalam tes Anda menyuntikkan ExecutorService berulir tunggal, seperti yang dari jambu biji:Jika kode Anda menggunakan forkJoinPool seperti
thenApplyAsync(...)
, tulis ulang kode untuk menggunakan ExecutorService (ada banyak alasan bagus), atau gunakan Awaitility.Untuk mempersingkat contoh, saya menjadikan BarService argumen metode yang diimplementasikan sebagai lambda Java8 dalam pengujian, biasanya itu akan menjadi referensi yang disuntikkan yang akan Anda tiru.
sumber
Saya lebih suka menggunakan tunggu dan beri tahu. Sederhana dan jelas.
Pada dasarnya, kita perlu membuat referensi Array akhir, untuk digunakan di dalam kelas batin anonim. Saya lebih suka membuat boolean [], karena saya bisa memberi nilai pada kontrol jika kita perlu menunggu (). Ketika semuanya selesai, kami hanya merilis asyncExecuted.
sumber
Untuk semua pengguna Spring di luar sana, inilah yang biasanya saya lakukan tes integrasi saya saat ini, di mana perilaku async terlibat:
Memecat peristiwa aplikasi dalam kode produksi, ketika tugas async (seperti panggilan I / O) telah selesai. Sebagian besar waktu acara ini diperlukan untuk menangani respons operasi async dalam produksi.
Dengan kejadian ini di tempat, Anda dapat menggunakan strategi berikut dalam kasus uji Anda:
Untuk memecah ini, pertama-tama Anda perlu semacam acara domain untuk memecat. Saya menggunakan UUID di sini untuk mengidentifikasi tugas yang telah selesai, tetapi Anda tentu saja bebas menggunakan sesuatu yang lain asalkan unik.
(Perhatikan, bahwa cuplikan kode berikut juga menggunakan anotasi Lombok untuk menghilangkan kode pelat ketel)
Kode produksi itu sendiri biasanya terlihat seperti ini:
Saya kemudian dapat menggunakan Spring
@EventListener
untuk menangkap acara yang diterbitkan dalam kode uji. Pendengar acara sedikit lebih terlibat, karena harus menangani dua kasus dengan cara yang aman:A
CountDownLatch
digunakan untuk kasus kedua seperti yang disebutkan dalam jawaban lain di sini. Juga perhatikan, bahwa@Order
anotasi pada metode event handler memastikan, bahwa metode handler event ini dipanggil setelah pendengar acara lain yang digunakan dalam produksi.Langkah terakhir adalah menjalankan sistem yang sedang diuji dalam suatu uji kasus. Saya menggunakan uji SpringBoot dengan JUnit 5 di sini, tetapi ini harus bekerja sama untuk semua tes menggunakan konteks Spring.
Perhatikan, bahwa berbeda dengan jawaban lain di sini, solusi ini juga akan berfungsi jika Anda menjalankan tes Anda secara paralel dan beberapa utas menggunakan kode async secara bersamaan.
sumber
Jika Anda ingin menguji logika, jangan mengujinya secara tidak sinkron.
Misalnya untuk menguji kode ini yang berfungsi pada hasil metode asinkron.
Dalam tes mengejek ketergantungan dengan implementasi sinkron. Tes unit sepenuhnya sinkron dan berjalan dalam 150 ms.
Anda tidak menguji perilaku async tetapi Anda dapat menguji apakah logikanya benar.
sumber