Tes unit yang menyatakan bahwa utas saat ini adalah utas utama

8

Pertanyaannya adalah apakah masuk akal untuk menulis unit test yang menyatakan bahwa utas saat ini adalah utas utama? Pro kontra?

Baru-baru ini saya telah melihat unit test yang menegaskan utas saat ini untuk panggilan balik layanan. Saya tidak yakin, itu ide yang bagus, saya percaya ini lebih seperti tes integrasi. Menurut pendapat saya, unit test harus menyatakan metode secara terpisah dan tidak boleh tahu tentang sifat konsumen layanan.

Misalnya, di iOS, pengguna layanan ini dimaksudkan untuk menjadi UI yang secara default memiliki kendala untuk menjalankan kode di utas utama.

Vladimir Kaltyrin
sumber
1
FWIW, dalam kode kami, daripada menjadikannya sebagai unit test assertion, kami menjadikannya sebagai pernyataan runtime, karena di situlah hal itu benar-benar penting.
user1118321
Tampak lebih seperti pernyataan daripada kasus uji yang sebenarnya.
whatsisname

Jawaban:

11

Biarkan saya menunjukkan kepada Anda prinsip unit test favorit saya:

Tes bukan tes unit jika:

  1. Itu berbicara ke database
  2. Ini berkomunikasi di seluruh jaringan
  3. Menyentuh sistem file
  4. Itu tidak dapat berjalan pada saat yang sama dengan tes unit Anda yang lain
  5. Anda harus melakukan hal-hal khusus pada lingkungan Anda (seperti mengedit file konfigurasi) untuk menjalankannya.

Seperangkat Aturan Pengujian Unit - Michael Feathers

Jika pengujian unit Anda bersikeras bahwa itu adalah "utas utama" sulit untuk tidak bertabrakan dengan nomor 4.

Ini mungkin tampak keras tetapi ingat bahwa tes unit hanyalah salah satu dari berbagai jenis tes.

masukkan deskripsi gambar di sini

Anda bebas melanggar prinsip-prinsip ini. Tetapi ketika Anda melakukannya, jangan menyebut tes Anda unit test. Jangan letakkan dengan tes unit nyata dan memperlambatnya.

candied_orange
sumber
Bagaimana Anda menguji unit logger? Apakah Anda harus moc IO? Tampaknya menjengkelkan.
whn
1
@opa jika logger Anda memiliki logika bisnis yang menarik, Anda mengisolasinya dan mengujinya. Kode membosankan yang mengonfirmasi bahwa kita memiliki sistem file tidak perlu pengujian unit.
candied_orange
@candied_orange Jadi sekarang Anda harus mengekspos logika internal? Karena bagaimana lagi Anda akan memisahkan string yang dihasilkan oleh logger sebelum dikeluarkan ke file dengan memisahkannya menjadi metode pribadi ?
whn
@opa Jika Anda perlu menguji apakah Anda dapat melakukannya dengan melewati aliran Anda sendiri. Unit test unit test bukan sumber daya sistem.
candied_orange
@candied_orange kecuali ketika logger hanya mengambil nama file yang seharusnya di output sebagai input. Intinya bukan bahwa "Tes unit tes unit bukan sumber daya sistem" salah, itu 100% benar. Anda tidak bisa begitu dogmatis dengan mengatakan ketika tes menyentuh file io bahwa itu bukan tes unit yang valid. Pengujian logger melalui membaca file output-nya adalah cara yang lebih mudah daripada membuat fungsi public private hanya demi pengujian unit, yang Anda benar-benar harus tidak pernah melakukan . Itu tidak menguji IO, itu menguji logger, itu hanya terjadi cara termudah untuk melakukannya adalah dengan membaca file jika Anda masih ingin kode bersih.
whn
5

Haruskah Anda menulis unit test yang menguji apakah utas adalah utas utama? Tergantung, tapi mungkin bukan ide yang bagus.

Jika titik unit test dimaksudkan untuk memverifikasi berfungsinya suatu metode, maka pengujian aspek ini hampir pasti tidak menguji aspek ini. Seperti yang Anda katakan sendiri, secara konseptual lebih merupakan tes integrasi, karena berfungsinya suatu metode tidak akan berubah berdasarkan pada thread mana yang menjalankannya, dan yang bertanggung jawab untuk menentukan thread mana yang digunakan adalah pemanggil dan bukan metode itu sendiri.

Namun, inilah mengapa saya mengatakan itu tergantung, karena mungkin sangat tergantung pada metode itu sendiri, harus metode menelurkan thread baru. Meskipun dalam semua kemungkinan ini bukan kasus Anda.

Saran saya akan meninggalkan cek ini keluar dari uji unit dan port ke tes integrasi yang tepat untuk aspek ini.

Neil
sumber
2

tidak boleh tahu tentang sifat konsumen layanan.

Ketika seorang konsumen mengkonsumsi suatu layanan ada "kontrak" yang tersurat atau tersirat tentang fungsi apa yang disediakan dan bagaimana. Batasan pada utas apa callback dapat terjadi adalah bagian dari kontrak itu.

Saya kira kode Anda berjalan dalam konteks di mana ada semacam "mesin acara" yang berjalan di "utas utama" dan cara bagi utas lain untuk meminta agar kode jadwal mesin acara dijalankan pada utas utama.

Dalam tampilan tes unit yang paling murni, Anda akan mengejek sepenuhnya setiap ketergantungan. Di dunia nyata sangat sedikit orang melakukan itu. Kode yang sedang diuji diizinkan untuk menggunakan versi nyata dari setidaknya fitur platform dasar.

Jadi pertanyaan sebenarnya IMO adalah apakah Anda menganggap mesin acara dan dukungan threading runtime menjadi bagian dari "fitur platform dasar" yang unit tes diizinkan untuk bergantung pada atau tidak? Jika Anda melakukannya maka masuk akal untuk memeriksa apakah panggilan balik terjadi pada "utas utama". Jika Anda mengejek acara mesin dan sistem threading maka Anda akan merancang tiruan Anda sehingga mereka dapat menentukan apakah panggilan balik akan terjadi pada utas utama. Jika Anda mengejek acara mesin tetapi bukan dukungan threading runtime Anda ingin menguji apakah panggilan balik terjadi pada utas tempat mesin acara tiruan Anda berjalan.

Peter Green
sumber
1

Saya bisa melihat mengapa itu akan menjadi ujian yang menarik. Tapi apa yang sebenarnya akan diuji? katakanlah kita memiliki fungsi

showMessage(string m)
{
    new DialogueBox(m).Show(); //will fail if not called from UI thread
}

Sekarang saya bisa menguji ini dan menjalankannya di utas non UI, membuktikan bahwa ada kesalahan. Tapi sungguh untuk tes yang memiliki nilai fungsi perlu memiliki beberapa perilaku spesifik yang Anda ingin lakukan ketika dijalankan di utas non-UI.

showMessage(string m)
{
    try {
        new DialogueBox(m).Show(); //will fail if not called from UI thread
    }
    catch {
        Logger.Log(m); //alternative display method
    }
}

Sekarang saya memiliki sesuatu untuk diuji, tetapi tidak jelas apakah alternatif semacam ini akan berguna dalam kehidupan nyata

Ewan
sumber
1

Jika panggilan balik ini didokumentasikan tidak aman untuk dipanggil selain dari, katakanlah, utas UI atau utas utama, maka tampaknya masuk akal dalam unit test modul kode yang menyebabkan permohonan panggilan balik ini untuk memastikan itu tidak melakukan sesuatu yang tidak aman dengan bekerja di luar utas bahwa OS atau kerangka kerja aplikasi didokumentasikan untuk menjamin aman.

hotpaw2
sumber
1

Susun Penegasan
Undang-Undang

Haruskah tes unit menyatakan bahwa itu ada di utas tertentu? Tidak, karena itu tidak menguji unit dalam kasus itu, itu bahkan bukan tes integrasi, karena tidak mengatakan apa-apa tentang konfigurasi yang terjadi di unit ...

Menegaskan di mana utas pengujian aktif, akan seperti Menegaskan nama server pengujian Anda, atau lebih baik lagi nama metode pengujian.

Sekarang, mungkin masuk akal untuk mengatur agar tes dijalankan pada utas tertentu, tetapi hanya jika Anda ingin memastikan bahwa itu akan mengembalikan hasil spesifik berdasarkan utas.

jmoreno
sumber
1

Jika Anda memiliki fungsi yang didokumentasikan untuk hanya berjalan dengan benar di utas utama, dan fungsi itu disebut di utas latar belakang, itu bukan kesalahan fungsi. Kesalahannya adalah kesalahan pemanggil, sehingga pengujian unit tidak ada gunanya.

Jika kontrak diubah sehingga fungsi akan berjalan dengan benar di utas utama, dan melaporkan kegagalan saat dijalankan pada utas latar, Anda dapat menulis unit test untuk itu, di mana Anda menyebutnya sekali pada utas utama dan kemudian pada utas latar belakang dan periksa apakah berperilaku seperti yang didokumentasikan. Jadi sekarang Anda memiliki unit test, tetapi bukan unit test yang memeriksa apakah itu dijalankan pada utas utama.

Saya akan sangat menyarankan bahwa baris pertama dari fungsi Anda harus menyatakan bahwa itu berjalan di utas utama.

gnasher729
sumber
1

Pengujian unit hanya berguna jika menguji implementasi yang benar dari fungsi tersebut, bukan penggunaan yang benar, karena pengujian unit tidak dapat mengatakan bahwa suatu fungsi tidak akan dipanggil dari utas lain di seluruh basis kode Anda, seperti yang bisa dilakukan ' t mengatakan bahwa fungsi tidak akan dipanggil dengan parameter yang melanggar prasyaratnya (dan secara teknis apa yang Anda coba uji pada dasarnya merupakan pelanggaran prasyarat dalam penggunaan, yang merupakan sesuatu yang tidak dapat Anda uji secara efektif karena tes dapat ' t membatasi cara tempat lain dalam basis kode menggunakan fungsi tersebut; Anda dapat menguji apakah pelanggaran terhadap prasyarat menghasilkan kesalahan / pengecualian yang wajar, namun).

Ini adalah kasus penegasan bagi saya dalam implementasi fungsi yang relevan sendiri seperti yang beberapa orang lain tunjukkan, atau bahkan lebih canggih adalah untuk memastikan fungsi tersebut aman dari thread (meskipun ini tidak selalu praktis ketika bekerja dengan beberapa API).

Juga hanya catatan samping tapi "utas utama"! = "Utas UI" dalam semua kasus. Banyak API GUI yang tidak aman-utas (dan membuat kit GUI yang aman-utas sangat sulit), tetapi itu tidak berarti Anda harus memintanya dari utas yang sama dengan yang memiliki titik masuk untuk aplikasi Anda. Itu mungkin berguna bahkan dalam mengimplementasikan pernyataan Anda di dalam fungsi UI yang relevan untuk membedakan "utas UI" dari "utas utama", seperti menangkap ID utas saat ini ketika sebuah jendela dibuat untuk dibandingkan dengan bukan dari titik masuk utama aplikasi (yang setidaknya mengurangi jumlah asumsi / batasan penggunaan yang diterapkan hanya oleh apa yang benar-benar relevan).

Keamanan benang sebenarnya adalah titik pijakan "gotcha" di bekas tim saya, dan dalam kasus khusus kami, saya akan menyebutnya sebagai "optimasi mikro" yang paling kontra-produktif di antara mereka semua yang menghasilkan lebih banyak biaya perawatan daripada rakitan tulisan tangan. Kami memiliki cakupan kode yang agak komprehensif dalam pengujian unit kami, bersama dengan tes integrasi yang agak canggih, hanya untuk menghadapi kebuntuan dan kondisi balapan dalam perangkat lunak yang menghindari pengujian kami. Dan itu karena pengembang kode multithreaded sembarangan tanpa menyadari setiap efek samping yang mungkin terjadi dalam rantai panggilan fungsi yang akan dihasilkan dari mereka sendiri, dengan ide yang agak naif bahwa mereka dapat memperbaiki bug tersebut di belakang dengan hanya melempar mengunci di kiri dan kanan,

Saya condong ke arah yang berlawanan sebagai tipe sekolah tua yang tidak mempercayai multithreading, adalah orang yang benar-benar terlambat untuk merangkulnya, dan berpikir kebenaran mengalahkan kinerja hingga jarang sekali menggunakan semua core yang kita miliki sekarang, sampai saya menemukan hal-hal seperti fungsi murni dan desain yang tidak berubah dan struktur data yang gigih yang akhirnya memungkinkan saya untuk sepenuhnya menggunakan perangkat keras itu tanpa khawatir di dunia tentang kondisi balapan dan kebuntuan. Saya harus mengakui bahwa sepanjang jalan hingga 2010 atau lebih, saya benci multithreading dengan penuh semangat kecuali untuk beberapa loop paralel di sana-sini di daerah-daerah yang sepele dengan alasan keamanan benang, dan lebih menyukai kode yang lebih berurutan untuk desain produk yang diberikan kesedihan saya dengan multithreading di bekas tim.

Bagi saya cara multithreading yang pertama dan memperbaiki bug kemudian adalah strategi yang mengerikan untuk multithreading sampai hampir membuat saya membenci multithreading pada awalnya; Anda juga memastikan desain Anda aman dari benang-solid dan implementasinya hanya menggunakan fungsi dengan jaminan yang sama (mis: fungsi murni), atau Anda menghindari multithreading. Itu mungkin sedikit dogmatis tetapi mengalahkan menemukan (atau lebih buruk, tidak menemukan) masalah yang sulit untuk direproduksi di belakang yang menghindari tes. Tidak ada gunanya mengoptimalkan mesin roket jika itu akan membuatnya rentan meledak tiba-tiba di tengah jalan sepanjang perjalanannya menuju ruang angkasa.

Jika Anda mau tidak mau harus bekerja dengan kode yang tidak aman thread, maka saya tidak melihat itu sebagai masalah untuk diselesaikan dengan pengujian unit / integrasi begitu banyak. Yang ideal adalah membatasi akses . Jika kode GUI Anda dipisahkan dari logika bisnis, maka Anda mungkin dapat menerapkan desain yang membatasi akses ke panggilan tersebut dari apa pun selain utas / objek yang membuatnya *. Mode yang jauh ideal bagi saya adalah membuatnya tidak mungkin untuk utas lain memanggil fungsi-fungsi itu daripada mencoba memastikannya tidak.

  • Ya, saya menyadari bahwa selalu ada cara di sekitar batasan desain apa pun yang Anda tegakkan secara khusus di mana kompiler tidak dapat melindungi Anda. Saya hanya berbicara secara praktis; jika Anda bisa mengabstraksi objek "GUI Thread" atau apa pun, maka itu mungkin satu-satunya yang menyerahkan parameter ke objek / fungsi GUI, dan Anda mungkin dapat membatasi objek tersebut dari memiliki akses ke utas lainnya. Tentu saja mungkin dapat memotong dan menggali lebih dalam dan bekerja di sekitar lingkaran untuk melewati fungsi / objek GUI ke utas lainnya untuk memohon, tetapi setidaknya ada penghalang di sana, dan Anda dapat memanggil siapa saja yang melakukan itu "idiot ", dan tidak salah, paling tidak, karena dengan jelas melewati dan mencari celah untuk apa yang jelas-jelas ingin dibatasi oleh desain. :-D Itu
Energi Naga
sumber