Bagaimana menghindari kebutuhan untuk menguji metode pribadi Unit

15

Saya tahu Anda tidak seharusnya menguji metode pribadi, dan jika Anda perlu melakukannya, mungkin ada kelas di sana yang menunggu untuk keluar.

Tapi, saya tidak ingin memiliki trilyun kelas hanya supaya saya dapat menguji antarmuka publik mereka dan saya menemukan bahwa untuk banyak kelas jika saya hanya menguji metode publik saya akhirnya harus mengejek banyak dependensi dan unit test adalah luar biasa dan sulit diikuti.

Saya lebih suka mengejek metode pribadi saat menguji yang publik, dan mengejek dependensi eksternal ketika menguji yang pribadi.

Apakah saya gila?

Fran Sevillano
sumber
Tes unit menyeluruh harus secara implisit mencakup semua anggota pribadi dari kelas yang diberikan, karena sementara Anda tidak dapat memanggil mereka secara langsung, perilaku mereka masih akan berpengaruh pada output. Jika tidak, mengapa mereka ada di tempat pertama? Ingat dalam pengujian unit apa yang Anda pedulikan adalah hasilnya, bukan bagaimana hasilnya sampai.
GordonM

Jawaban:

24

Anda sebagian benar - Anda tidak boleh langsung menguji metode pribadi. Metode pribadi pada kelas harus dipanggil oleh satu atau lebih metode publik (mungkin secara tidak langsung - metode pribadi yang disebut dengan metode publik dapat menggunakan metode pribadi lainnya). Karena itu, ketika menguji metode publik Anda, Anda akan menguji metode pribadi Anda juga. Jika Anda memiliki metode pribadi yang masih belum teruji, kasus pengujian Anda tidak mencukupi atau metode pribadi tidak digunakan dan dapat dihapus.

Jika Anda mengambil pendekatan pengujian kotak putih, Anda harus mempertimbangkan detail implementasi metode pribadi Anda saat menyusun tes unit di sekitar metode publik Anda. Jika Anda mengambil pendekatan kotak hitam, Anda tidak boleh menguji terhadap setiap detail implementasi baik dalam metode publik atau pribadi tetapi terhadap perilaku yang diharapkan.

Secara pribadi, saya lebih suka pendekatan kotak putih untuk tes unit. Saya dapat membuat tes untuk menempatkan metode dan kelas yang diuji di berbagai negara yang menyebabkan perilaku menarik dalam metode publik dan pribadi saya dan kemudian menyatakan bahwa hasilnya adalah apa yang saya harapkan.

Jadi - jangan mengejek metode pribadi Anda. Gunakan mereka untuk memahami apa yang perlu Anda uji untuk memberikan cakupan fungsionalitas yang Anda berikan. Ini terutama berlaku pada tingkat unit test.

Thomas Owens
sumber
1
"jangan mengejek metode pribadi Anda" ya, saya bisa mengerti pengujian, ketika Anda mengejek mereka, Anda mungkin telah melewati garis halus menjadi gila
Ewan
Ya tapi seperti yang saya katakan, masalah saya dengan pengujian kotak putih adalah bahwa biasanya mengejek semua dependensi untuk metode publik dan pribadi membuat metode pengujian yang sangat besar. Ada ide bagaimana mengatasinya?
Fran Sevillano
8
@FranSevillano Jika Anda harus bertopik atau mengejek sebanyak itu, saya akan melihat keseluruhan desain Anda. Sesuatu terasa aneh.
Thomas Owens
Alat cakupan kode membantu dengan ini.
Andy
5
@FranSevillano: Tidak ada banyak alasan bagus untuk kelas yang melakukan satu hal untuk memiliki banyak dependensi. Jika Anda memiliki banyak ketergantungan, Anda mungkin memiliki kelas Dewa.
Mooing Duck
4

Saya pikir ini ide yang sangat buruk.

Masalah dengan membuat unit test anggota pribadi, adalah bahwa itu tidak cocok dengan siklus hidup produk.

Alasan Anda MEMILIH membuat metode-metode itu privat adalah karena mereka tidak terpusat pada apa yang coba dilakukan oleh kelas Anda - hanya membantu bagaimana Anda saat ini mengimplementasikan fungsionalitas itu. Ketika Anda refactor, detail-detail pribadi tersebut kemungkinan akan berubah dan akan menyebabkan gesekan dengan refactoring.

Juga, perbedaan utama antara anggota publik dan pribadi, adalah Anda harus memikirkan API publik Anda dengan saksama, dan mendokumentasikannya dengan baik, dan memeriksanya dengan baik (pernyataan dll). Tetapi dengan API pribadi, tidak ada gunanya untuk berpikir dengan hati-hati (usaha yang sia-sia karena penggunaannya sangat lokal). Memasukkan metode pribadi ke dalam unit test berarti menciptakan ketergantungan eksternal pada metode tersebut. Berarti API harus stabil dan didokumentasikan dengan baik (karena seseorang harus mencari tahu mengapa tes unit gagal jika / ketika mereka melakukannya).

Saya menyarankan Anda:

  • RETHINK apakah metode tersebut harus bersifat pribadi
  • Tulis tes sekali pakai (hanya untuk memastikan itu benar sekarang, buat publik untuk sementara sehingga Anda dapat menguji, dan kemudian hapus tes)
  • Dibangun pengujian bersyarat ke dalam implementasi Anda menggunakan #ifdef, dan pernyataan (pengujian hanya dilakukan di build debug).

A menghargai kecenderungan Anda untuk menguji fungsi ini dan sulit untuk mengujinya melalui API publik Anda saat ini. Tapi saya pribadi lebih menghargai modularitas daripada cakupan tes.

Lewis Pringle
sumber
6
Saya tidak setuju. IMO, ini 100% benar untuk tes integrasi. Tetapi dengan unit test, semuanya berbeda; Tujuan pengujian unit adalah untuk menentukan di mana bug berada, cukup sempit sehingga Anda dapat memperbaikinya dengan cepat. Saya sering menemukan diri saya dalam situasi ini: saya punya sedikit metode publik karena itu benar-benar nilai inti dari kelas saya (seperti yang Anda katakan harus dilakukan). Namun, saya juga menghindari penulisan 400 metode garis, baik publik maupun pribadi. Jadi beberapa metode publik saya hanya dapat mencapai tujuannya dengan bantuan puluhan metode pribadi. Terlalu banyak kode untuk "cepat memperbaikinya". Saya harus memulai debugger dll.
marstato
6
@marstato: anjuran: mulailah menulis tes terlebih dahulu dan ubah pikiran Anda tentang unittests: mereka tidak menemukan bug, tetapi verifikasi bahwa kodenya berfungsi seperti yang diinginkan pengembang.
Timothy Truckle
@marstato Terima kasih! Iya. Tes selalu lulus pertama kali, ketika Anda memeriksa barang-barang di (atau Anda tidak akan memeriksanya!). Mereka berguna, ketika Anda mengembangkan kode, memberi Anda kepala ketika Anda memecahkan sesuatu, dan jika Anda memiliki tes regresi BAIK, maka mereka memberi Anda KENYAMANAN / PANDUAN tentang di mana mencari masalah (bukan pada hal-hal yang stabil , dan regresi yang diuji).
Lewis Pringle
@marstato "Tujuan pengujian unit adalah untuk menentukan di mana bug berada" - Itulah kesalahpahaman yang mengarah ke pertanyaan OP. Tujuan pengujian unit adalah untuk memverifikasi perilaku API yang dimaksudkan (dan lebih baik didokumentasikan).
user560822
4
@marstato Nama "tes integrasi" berasal dari pengujian bahwa beberapa komponen bekerja bersama (yaitu mereka berintegrasi dengan benar). Pengujian unit adalah pengujian komponen tunggal melakukan apa yang seharusnya dilakukan secara terpisah, yang pada dasarnya berarti bahwa API publiknya berfungsi seperti yang didokumentasikan / diperlukan. Tak satu pun dari istilah itu yang mengatakan apa pun tentang apakah Anda menyertakan tes logika implementasi internal sebagai bagian untuk memastikan integrasi bekerja, atau bahwa unit tunggal berfungsi.
Ben
3

UnitTests menguji perilaku publik yang dapat diamati , bukan kode, di mana "publik" berarti: mengembalikan nilai dan berkomunikasi dengan dependensi.

"Unit" adalah kode apa saja, yang memecahkan masalah yang sama (atau lebih tepatnya: memiliki alasan yang sama untuk berubah). Itu bisa berupa metode tunggal atau banyak kelas.

Alasan utama mengapa Anda tidak ingin menguji private methodsadalah: mereka adalah detail implementasi dan Anda mungkin ingin mengubahnya selama refactoring (tingkatkan kode Anda dengan menerapkan prinsip-prinsip OO tanpa mengubah fungsionalitas). Ini tepatnya ketika Anda tidak ingin unittests Anda berubah sehingga mereka dapat menjamin perilaku CuT Anda tidak berubah selama refactoring.

Tapi, saya tidak ingin memiliki trilyun kelas hanya supaya saya dapat menguji antarmuka publik mereka dan saya menemukan bahwa untuk banyak kelas jika saya hanya menguji metode publik saya akhirnya harus mengejek banyak dependensi dan unit test adalah luar biasa dan sulit diikuti.

Saya biasanya mengalami hal yang sebaliknya: Semakin kecil kelas (semakin sedikit tanggung jawab yang mereka miliki), semakin sedikit ketergantungan yang mereka miliki, dan semakin mudah pula mereka yang belum terdaftar, untuk menulis dan membaca.

Idealnya Anda menerapkan tingkat pola abstraksi yang sama untuk kelas Anda. Ini berarti kelas Anda menyediakan logika bisnis (lebih disukai sebagai "fungsi murni" yang bekerja pada parameter mereka hanya tanpa mempertahankan status sendiri) (x) atau memanggil metode pada objek lain, bukan keduanya pada saat yang bersamaan.

Dengan cara ini unittesting perilaku bisnis adalah sepotong kue dan objek "delegasi" biasanya terlalu mudah untuk gagal (tidak ada percabangan, tidak ada perubahan negara) sehingga tidak ada unittesting diperlukan dan pengujian mereka dapat diserahkan kepada tes integrasi atau modul

Timothy Truckle
sumber
1

Unit testing memiliki nilai ketika Anda adalah satu-satunya programmer yang mengerjakan kode, terutama jika aplikasi tersebut sangat besar atau sangat kompleks. Di mana pengujian unit menjadi penting adalah ketika Anda memiliki lebih banyak programmer yang bekerja pada basis kode yang sama. Konsep pengujian unit diperkenalkan untuk mengatasi beberapa kesulitan bekerja di tim yang lebih besar ini.

Alasan bahwa unit test membantu tim yang lebih besar adalah karena kontrak. Jika kode saya membuat panggilan ke kode yang ditulis oleh orang lain, saya membuat asumsi tentang apa yang akan dilakukan oleh kode orang lain dalam berbagai situasi. Asalkan asumsi tersebut masih benar, kode saya akan tetap berfungsi, tetapi bagaimana saya tahu asumsi yang valid dan bagaimana saya tahu kapan asumsi tersebut telah berubah?

Di sinilah tes unit masuk. Penulis kelas membuat tes unit untuk mendokumentasikan perilaku yang diharapkan dari kelas mereka. Uji unit mendefinisikan semua cara valid menggunakan kelas, dan menjalankan uji unit memvalidasi kasus penggunaan ini berfungsi seperti yang diharapkan. Programmer lain yang ingin memanfaatkan kelas Anda dapat membaca tes unit Anda untuk memahami perilaku yang dapat mereka harapkan untuk kelas Anda, dan menggunakan ini sebagai dasar untuk asumsi mereka tentang cara kerja kelas Anda.

Dengan cara ini metode publik menandatangani kelas dan unit test bersama-sama membentuk kontrak antara penulis kelas dan programmer lain yang menggunakan kelas ini dalam kode mereka.

Dalam skenario ini, apa yang terjadi jika Anda memasukkan pengujian metode pribadi? Jelas itu tidak masuk akal.

Jika Anda adalah satu-satunya programmer yang bekerja pada kode Anda dan Anda ingin menggunakan unit testing sebagai cara debug kode Anda maka saya tidak melihat ada salahnya, itu hanya alat, dan Anda dapat menggunakannya dengan cara apa pun yang bekerja untuk Anda, tetapi itu bukan alasan mengapa pengujian unit diperkenalkan, dan tidak memberikan manfaat utama pengujian unit.

bikeman868
sumber
Saya berjuang dengan ini di mana saya mengejek dependensi sehingga jika ada dependensi mengubah perilaku itu, tes saya tidak akan gagal. Apakah kegagalan ini seharusnya terjadi dalam tes integrasi bukan tes unit?
Todd
Mock seharusnya berperilaku identik dengan implementasi nyata, tetapi tidak memiliki ketergantungan. Jika implementasi nyata berubah sehingga kini berbeda, ini akan ditampilkan sebagai kegagalan pengujian integrasi. Secara pribadi saya menganggap pengembangan tiruan dan implementasi nyata harus dilakukan menjadi satu tugas. Saya tidak membangun ejekan sebagai bagian dari tes unit menulis. Dengan cara ini ketika saya mengubah perilaku kelas saya dan mengubah mock untuk mencocokkan, menjalankan tes unit akan mengidentifikasi semua kelas lain yang rusak oleh perubahan ini.
bikeman868
1

Sebelum menjawab pertanyaan seperti itu, Anda perlu memutuskan apa yang sebenarnya ingin Anda capai.

Anda menulis kode. Anda berharap itu memenuhi kontraknya (dengan kata lain, itu melakukan apa yang seharusnya dilakukan. Menulis apa yang seharusnya dilakukan adalah langkah besar bagi sebagian orang).

Agar cukup yakin bahwa kode melakukan apa yang seharusnya dilakukan, Anda cukup menatapnya, atau menulis kode uji yang menguji cukup banyak kasus untuk meyakinkan Anda "jika kode tersebut lulus semua tes ini maka itu benar".

Seringkali Anda hanya tertarik pada antarmuka yang ditentukan untuk umum dari beberapa kode. Jika saya menggunakan perpustakaan Anda, saya tidak peduli bagaimana Anda membuatnya bekerja dengan benar, hanya itu tidak bekerja dengan benar. Saya memverifikasi bahwa perpustakaan Anda benar dengan melakukan tes unit.

Tetapi Anda sedang membuat perpustakaan. Membuatnya berfungsi dengan benar bisa jadi sulit untuk dicapai. Katakanlah saya hanya peduli dengan perpustakaan yang melakukan operasi X dengan benar, jadi saya memiliki unit test untuk X. Anda, pengembang yang bertanggung jawab untuk membuat perpustakaan, mengimplementasikan X dengan menggabungkan langkah A, B dan C, yang masing-masing sama sekali tidak berguna. Agar perpustakaan Anda berfungsi, Anda menambahkan tes untuk memverifikasi bahwa A, B, dan C masing-masing berfungsi dengan benar. Anda ingin tes ini. Mengatakan "Anda seharusnya tidak memiliki tes unit untuk metode pribadi" tidak ada gunanya. Anda ingin tes untuk metode pribadi ini. Mungkin seseorang memberi tahu Anda bahwa unit yang menguji metode pribadi salah. Tapi itu hanya berarti Anda mungkin tidak menyebutnya "unit test" tetapi "tes pribadi" atau apa pun yang Anda suka menyebutnya.

Bahasa Swift memecahkan masalah yang Anda tidak ingin mengekspos A, B, C sebagai metode publik hanya karena Anda ingin mengujinya dengan memberi fungsi atribut "dapat diuji". Kompiler memungkinkan metode yang dapat diuji secara pribadi dipanggil dari unit test, tetapi bukan dari kode non-test.

gnasher729
sumber
0

Ya, Anda gila .... SEPERTI FOX!

Ada beberapa cara untuk menguji metode pribadi, beberapa di antaranya tergantung pada bahasa.

  • refleksi! peraturan dibuat untuk dilanggar!
  • membuat mereka terlindungi, mewarisi dan menimpa
  • teman / InternalsVisibleUntuk kelas

Secara keseluruhan, jika Anda ingin menguji metode pribadi, Anda mungkin ingin memindahkannya ke metode publik pada ketergantungan dan menguji / menyuntikkan itu.

Ewan
sumber
Saya tidak tahu, di mata saya Anda menjelaskan bahwa itu bukan ide yang baik tetapi terus menjawab pertanyaan
Liath
Saya pikir saya memiliki semua pangkalan yang dibahas :(
Ewan
3
Saya memilih, karena gagasan mengungkap metode pembantu pribadi Anda ke API publik hanya untuk mengujinya gila, dan saya selalu berpikir begitu.
Robert Harvey
0

Tetap pragmatis. Menguji kasus khusus dalam metode pribadi dengan cara mengatur keadaan instance dan parameter ke metode publik sehingga kasus-kasus tersebut terjadi di sana, seringkali jauh terlalu rumit.

Saya menambahkan internalaccessor tambahan (bersama-sama dengan bendera InternalsVisibleTorakitan pengujian), dengan nama yang jelas DoSomethingForTesting(parameters), untuk menguji metode "pribadi" tersebut.

Tentu saja, implementasinya dapat berubah kapan saja, dan tes-tes tersebut termasuk pengakses tes menjadi usang. Itu masih lebih baik daripada kasus yang belum diuji atau tes yang tidak dapat dibaca.

Bernhard Hiller
sumber