Bagaimana cara saya menguji unit (menggunakan xUnit) kelas yang memiliki metode privat internal, bidang atau kelas bersarang? Atau fungsi yang dibuat pribadi dengan memiliki tautan internal ( static
dalam C / C ++) atau dalam namespace pribadi ( anonim )?
Tampaknya buruk untuk mengubah pengubah akses untuk suatu metode atau fungsi hanya untuk dapat menjalankan tes.
java
unit-testing
tdd
Raedwald
sumber
sumber
Jawaban:
Jika Anda memiliki sedikit aplikasi Java lawas , dan Anda tidak diizinkan untuk mengubah visibilitas metode Anda, cara terbaik untuk menguji metode pribadi adalah dengan menggunakan refleksi .
Secara internal kami menggunakan bantuan untuk mendapatkan / mengatur
private
danprivate static
variabel serta memohonprivate
danprivate static
metode. Pola-pola berikut akan memungkinkan Anda melakukan hampir semua hal yang berhubungan dengan metode dan bidang pribadi. Tentu saja, Anda tidak dapat mengubahprivate static final
variabel melalui refleksi.Dan untuk bidang:
sumber
Cara terbaik untuk menguji metode pribadi adalah melalui metode publik lainnya. Jika ini tidak dapat dilakukan, maka salah satu dari kondisi berikut ini benar:
sumber
Ketika saya memiliki metode pribadi di kelas yang cukup rumit sehingga saya merasa perlu untuk menguji metode pribadi secara langsung, itu adalah bau kode: kelas saya terlalu rumit.
Pendekatan saya yang biasa untuk mengatasi masalah seperti itu adalah untuk menggoda kelas baru yang berisi bit-bit menarik. Seringkali, metode ini dan bidang yang berinteraksi dengannya, dan mungkin satu atau dua metode lain dapat diekstraksi ke kelas baru.
Kelas baru memperlihatkan metode ini sebagai 'publik', sehingga mereka dapat diakses untuk pengujian unit. Kelas-kelas baru dan lama sekarang lebih sederhana daripada kelas asli, yang bagus untuk saya (saya harus menjaga hal-hal sederhana, atau saya tersesat!)
Perhatikan bahwa saya tidak menyarankan agar orang membuat kelas tanpa menggunakan otak mereka! Intinya di sini adalah menggunakan kekuatan pengujian unit untuk membantu Anda menemukan kelas baru yang baik.
sumber
Saya telah menggunakan refleksi untuk melakukan ini untuk Jawa di masa lalu, dan menurut saya itu adalah kesalahan besar.
Sebenarnya, Anda tidak boleh menulis unit test yang langsung menguji metode pribadi. Yang harus Anda uji adalah kontrak publik yang dimiliki kelas dengan benda lain; Anda tidak boleh secara langsung menguji internal objek. Jika pengembang lain ingin membuat perubahan internal kecil ke kelas, yang tidak memengaruhi kontrak publik kelas, ia kemudian harus memodifikasi tes berbasis refleksi Anda untuk memastikan bahwa itu berfungsi. Jika Anda melakukan ini berulang kali di seluruh proyek, unit test kemudian berhenti menjadi pengukuran kesehatan kode yang berguna, dan mulai menjadi penghalang bagi pengembangan, dan gangguan pada tim pengembangan.
Apa yang saya sarankan lakukan adalah menggunakan alat cakupan kode seperti Cobertura, untuk memastikan bahwa tes unit yang Anda tulis memberikan cakupan kode yang layak dalam metode pribadi. Dengan begitu, Anda secara tidak langsung menguji apa yang dilakukan metode pribadi, dan mempertahankan tingkat kelincahan yang lebih tinggi.
sumber
Dari artikel ini: Menguji Metode Pribadi dengan JUnit dan SuiteRunner (Bill Venners), Anda pada dasarnya memiliki 4 opsi:
sumber
Umumnya tes unit dimaksudkan untuk menggunakan antarmuka publik dari suatu kelas atau unit. Oleh karena itu, metode pribadi adalah detail implementasi yang tidak Anda harapkan akan diuji secara eksplisit.
sumber
Hanya dua contoh di mana saya ingin menguji metode pribadi:
SecurityManager
tidak dikonfigurasi untuk mencegah hal ini).Saya mengerti ide hanya menguji "kontrak". Tapi saya tidak melihat ada yang bisa menganjurkan sebenarnya tidak menguji kode - jarak tempuh Anda mungkin bervariasi.
Jadi pengorbanan saya melibatkan menyulitkan JUnits dengan refleksi, daripada mengorbankan keamanan & SDK saya.
sumber
private
bukan satu-satunya alternatif. Tidak ada pengubah akses yangpackage private
berarti Anda dapat mengujinya selama pengujian unit Anda berlangsung dalam paket yang sama.Metode pribadi disebut dengan metode publik, jadi input untuk metode publik Anda juga harus menguji metode pribadi yang dipanggil oleh metode publik tersebut. Ketika metode publik gagal, maka itu bisa menjadi kegagalan dalam metode pribadi.
sumber
Pendekatan lain yang saya gunakan adalah mengubah metode pribadi untuk mengemas pribadi atau dilindungi kemudian melengkapinya dengan penjelasan @VisibleForTesting dari pustaka Google Guava.
Ini akan memberi tahu siapa pun yang menggunakan metode ini untuk berhati-hati dan tidak mengaksesnya langsung bahkan dalam sebuah paket. Juga kelas tes tidak harus dalam paket yang sama secara fisik , tetapi dalam paket yang sama di bawah folder tes .
Misalnya, jika metode yang akan diuji ada
src/main/java/mypackage/MyClass.java
maka panggilan uji Anda harus dimasukkansrc/test/java/mypackage/MyClassTest.java
. Dengan begitu, Anda mendapat akses ke metode tes di kelas tes Anda.sumber
Untuk menguji kode lama dengan kelas besar dan unik, seringkali sangat membantu untuk dapat menguji metode pribadi (atau publik) yang saya tulis saat ini .
Saya menggunakan paket- junitx.util.PrivateAccessor untuk Java. Banyak one-liner bermanfaat untuk mengakses metode pribadi dan bidang pribadi.
sumber
Class
parameter pertama Anda dalam metode ini, Anda hanya mengaksesstatic
anggota.Dalam Kerangka Kerja Spring Anda dapat menguji metode pribadi menggunakan metode ini:
Sebagai contoh:
sumber
Setelah mencoba solusi Cem Catikkas menggunakan refleksi untuk Jawa, saya harus mengatakan bahwa itu adalah solusi yang lebih elegan daripada yang saya jelaskan di sini. Namun, jika Anda mencari alternatif untuk menggunakan refleksi, dan memiliki akses ke sumber yang Anda uji, ini masih akan menjadi pilihan.
Ada kemungkinan manfaat dalam menguji metode privat suatu kelas, khususnya dengan pengembangan berbasis tes , di mana Anda ingin merancang tes kecil sebelum Anda menulis kode apa pun.
Membuat tes dengan akses ke anggota pribadi dan metode dapat menguji area kode yang sulit untuk ditargetkan secara khusus dengan akses hanya ke metode publik. Jika metode publik memiliki beberapa langkah yang terlibat, itu dapat terdiri dari beberapa metode pribadi, yang kemudian dapat diuji secara individual.
Keuntungan:
Kekurangan:
Namun, jika pengujian terus-menerus memerlukan metode ini, itu mungkin merupakan sinyal bahwa metode pribadi harus diekstraksi, yang dapat diuji dengan cara tradisional dan publik.
Berikut adalah contoh berbelit-belit tentang bagaimana ini akan bekerja:
Kelas dalam akan dikompilasi
ClassToTest$StaticInnerTest
.Lihat juga: Java Tip 106: Kelas dalam statis untuk kesenangan dan keuntungan
sumber
Seperti yang orang lain katakan ... jangan menguji metode pribadi secara langsung. Berikut ini beberapa pemikiran:
Jalankan cakupan kode pada unit test. Jika Anda melihat bahwa metode tidak sepenuhnya diuji tambahkan ke tes untuk mendapatkan cakupan. Bertujuan untuk cakupan kode 100%, tetapi sadarilah bahwa Anda mungkin tidak akan mendapatkannya.
sumber
Metode pribadi dikonsumsi oleh yang umum. Kalau tidak, itu kode mati. Itu sebabnya Anda menguji metode publik, menegaskan hasil yang diharapkan dari metode publik dan dengan demikian, metode pribadi yang dikonsumsinya.
Menguji metode pribadi harus diuji dengan men-debug sebelum menjalankan pengujian unit Anda pada metode publik.
Mereka juga dapat didebug menggunakan pengembangan yang digerakkan oleh tes, men-debug tes unit Anda sampai semua pernyataan Anda dipenuhi.
Saya pribadi percaya bahwa lebih baik membuat kelas menggunakan TDD; membuat bertopik metode publik, kemudian menghasilkan unit test dengan semua pernyataan yang ditetapkan sebelumnya, sehingga hasil yang diharapkan dari metode ini ditentukan sebelum Anda mengkodekannya. Dengan cara ini, Anda tidak salah jalan membuat pernyataan pengujian unit cocok dengan hasilnya. Kelas Anda kemudian kuat dan memenuhi persyaratan ketika semua tes unit Anda lulus.
sumber
Jika menggunakan Spring, ReflectionTestUtils menyediakan beberapa alat praktis yang membantu di sini dengan upaya minimal. Misalnya, untuk membuat tiruan pada anggota pribadi tanpa dipaksa untuk menambahkan setter publik yang tidak diinginkan:
sumber
Jika Anda mencoba menguji kode yang ada yang enggan atau tidak dapat Anda ubah, refleksi adalah pilihan yang baik.
Jika desain kelas masih fleksibel, dan Anda memiliki metode pribadi yang rumit yang ingin Anda uji secara terpisah, saya sarankan Anda menariknya ke kelas yang terpisah dan menguji kelas itu secara terpisah. Ini tidak harus mengubah antarmuka publik dari kelas asli; itu secara internal dapat membuat instance dari kelas helper dan memanggil metode helper.
Jika Anda ingin menguji kondisi kesalahan yang sulit yang berasal dari metode helper, Anda dapat melangkah lebih jauh. Ekstrak antarmuka dari kelas helper, tambahkan pengambil publik dan setter ke kelas asli untuk menyuntikkan kelas helper (digunakan melalui antarmuka-nya), dan kemudian menyuntikkan versi tiruan dari kelas helper ke dalam kelas asli untuk menguji bagaimana kelas asli menanggapi pengecualian dari helper. Pendekatan ini juga membantu jika Anda ingin menguji kelas asli tanpa juga menguji kelas pembantu.
sumber
Menguji metode pribadi merusak enkapsulasi kelas Anda karena setiap kali Anda mengubah implementasi internal Anda melanggar kode klien (dalam hal ini, tes).
Jadi jangan menguji metode pribadi.
sumber
Jawaban dari halaman FAQ JUnit.org :
PS Saya kontributor utama untuk Dp4j , tanyakan kepada saya jika Anda membutuhkan bantuan. :)
sumber
Jika Anda ingin menguji metode pribadi dari aplikasi lawas di mana Anda tidak dapat mengubah kode, satu opsi untuk Java adalah jMockit , yang akan memungkinkan Anda untuk membuat tiruan ke objek bahkan ketika mereka pribadi ke kelas.
sumber
Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Saya cenderung tidak menguji metode pribadi. Disinilah letak kegilaan. Secara pribadi, saya percaya Anda hanya harus menguji antarmuka Anda yang terbuka untuk umum (dan itu termasuk metode yang dilindungi dan internal).
sumber
Jika Anda menggunakan JUnit, lihatlah junit-addons . Ini memiliki kemampuan untuk mengabaikan model keamanan Java dan mengakses metode dan atribut pribadi.
sumber
Saya sarankan Anda sedikit refactoring kode Anda. Ketika Anda harus mulai berpikir tentang menggunakan refleksi atau hal-hal lain, untuk hanya menguji kode Anda, ada yang salah dengan kode Anda.
Anda menyebutkan berbagai jenis masalah. Mari kita mulai dengan bidang pribadi. Dalam kasus bidang pribadi saya akan menambahkan konstruktor baru dan menyuntikkan bidang ke dalamnya. Alih-alih ini:
Saya akan menggunakan ini:
Ini tidak akan menjadi masalah bahkan dengan beberapa kode lawas. Kode lama akan menggunakan konstruktor kosong, dan jika Anda bertanya kepada saya, kode refactored akan terlihat lebih bersih, dan Anda akan dapat menyuntikkan nilai yang diperlukan dalam pengujian tanpa refleksi.
Sekarang tentang metode pribadi. Dalam pengalaman pribadi saya ketika Anda harus mematikan metode pribadi untuk pengujian, maka metode itu tidak ada hubungannya di kelas itu. Pola umum, dalam hal ini, adalah membungkusnya dalam sebuah antarmuka, seperti
Callable
dan kemudian Anda memasukkan antarmuka itu juga dalam konstruktor (dengan beberapa trik konstruktor):Kebanyakan yang saya tulis sepertinya merupakan pola injeksi ketergantungan. Dalam pengalaman pribadi saya, ini sangat berguna saat pengujian, dan saya pikir kode semacam ini lebih bersih dan lebih mudah dirawat. Saya akan mengatakan hal yang sama tentang kelas bersarang. Jika kelas bersarang mengandung logika berat, akan lebih baik jika Anda memindahkannya sebagai paket kelas privat dan telah menyuntikkannya ke kelas yang membutuhkannya.
Ada juga beberapa pola desain lain yang telah saya gunakan saat refactoring dan mempertahankan kode lama, tetapi semuanya tergantung pada kasus kode Anda untuk diuji. Menggunakan refleksi sebagian besar bukan masalah, tetapi ketika Anda memiliki aplikasi perusahaan yang sangat diuji dan tes dijalankan sebelum setiap penyebaran semuanya menjadi sangat lambat (itu hanya mengganggu dan saya tidak suka hal-hal semacam itu).
Ada juga injeksi setter, tetapi saya tidak akan merekomendasikan menggunakannya. Saya lebih baik tetap dengan konstruktor dan menginisialisasi semuanya ketika itu benar-benar diperlukan, meninggalkan kemungkinan untuk menyuntikkan dependensi yang diperlukan.
sumber
ClassToTest(Callable)
suntikan. Itu membuatClassToTest
lebih rumit. Tetap sederhana. Juga, itu kemudian mengharuskan orang lain untuk mengatakanClassToTest
sesuatu yangClassToTest
seharusnya menjadi bos. Ada tempat untuk menyuntikkan logika, tetapi ini bukan. Anda baru saja membuat kelas lebih sulit untuk dipertahankan, tidak mudah.X
Anda tidak meningkatkanX
kompleksitas ke titik di mana hal itu dapat menyebabkan lebih banyak masalah ... dan karena itu memerlukan pengujian tambahan ... yang jika Anda telah menerapkan dengan cara yang dapat menyebabkan lebih banyak masalah .. (ini bukan loop yang tak terbatas; setiap iterasi cenderung lebih kecil dari yang sebelumnya, tetapi masih menjengkelkan)ClassToTest
lebih sulit untuk dipertahankan? Sebenarnya itu membuat aplikasi Anda lebih mudah untuk dipelihara, apa yang Anda sarankan untuk membuat kelas baru untuk setiap nilai berbeda yang Anda perlukanfirst
dan variabel 'kedua'?class A{ A(){} f(){} }
lebih sederhana dariclass A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }
. Jika itu tidak benar, dan jika benar bahwa memasok logika kelas sebagai argumen konstruktor lebih mudah dipertahankan, maka kita akan melewatkan semua logika sebagai argumen konstruktor. Saya hanya tidak melihat bagaimana Anda dapat mengklaimclass B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }
pernah membuat A lebih mudah dipertahankan. Ada kasus di mana menyuntikkan seperti ini masuk akal, tapi saya tidak melihat contoh Anda sebagai "lebih mudah untuk mempertahankan"Metode pribadi hanya dapat diakses dalam kelas yang sama. Jadi tidak ada cara untuk menguji metode "pribadi" dari kelas target dari kelas tes mana pun. Jalan keluarnya adalah Anda dapat melakukan pengujian unit secara manual atau dapat mengubah metode Anda dari "pribadi" menjadi "dilindungi".
Dan kemudian metode yang dilindungi hanya dapat diakses dalam paket yang sama di mana kelas didefinisikan. Jadi, menguji metode yang dilindungi dari kelas target berarti kami perlu mendefinisikan kelas uji Anda dalam paket yang sama dengan kelas target.
Jika semua hal di atas tidak sesuai dengan kebutuhan Anda, gunakan cara refleksi untuk mengakses metode pribadi.
sumber
Seperti yang banyak disarankan di atas, cara yang baik adalah mengujinya melalui antarmuka publik Anda.
Jika Anda melakukan ini, sebaiknya gunakan alat jangkauan kode (seperti Emma) untuk melihat apakah metode pribadi Anda sebenarnya dieksekusi dari pengujian Anda.
sumber
Inilah fungsi generik saya untuk menguji bidang pribadi:
sumber
Silakan lihat di bawah untuk contoh;
Pernyataan impor berikut harus ditambahkan:
Sekarang Anda dapat langsung melewati objek yang memiliki metode pribadi, nama metode untuk dipanggil, dan parameter tambahan seperti di bawah ini.
sumber
Hari ini, saya mendorong perpustakaan Java untuk membantu menguji metode dan bidang pribadi. Ini telah dirancang dengan mempertimbangkan Android, tetapi benar-benar dapat digunakan untuk proyek Java apa pun.
Jika Anda mendapatkan kode dengan metode atau bidang atau konstruktor pribadi, Anda bisa menggunakan BoundBox . Itu tidak persis apa yang Anda cari. Di bawah ini adalah contoh dari tes yang mengakses dua bidang pribadi dari aktivitas Android untuk mengujinya:
BoundBox memudahkan untuk menguji bidang, metode, dan konstruktor pribadi / yang dilindungi. Anda bahkan dapat mengakses hal-hal yang disembunyikan oleh warisan. Memang, BoundBox memecah enkapsulasi. Ini akan memberi Anda akses ke semua itu melalui refleksi, TETAPI semuanya diperiksa pada waktu kompilasi.
Ini sangat ideal untuk menguji beberapa kode lama. Gunakan dengan hati-hati. ;)
https://github.com/stephanenicolas/boundbox
sumber
Pertama, saya akan mengajukan pertanyaan ini: Mengapa anggota pribadi Anda perlu pengujian terisolasi? Apakah mereka begitu kompleks, memberikan perilaku yang rumit sehingga memerlukan pengujian terpisah dari permukaan publik? Ini unit testing, bukan pengujian 'line-of-code'. Jangan memusingkan hal-hal kecil.
Jika mereka sebesar itu, cukup besar sehingga anggota pribadi ini masing-masing merupakan 'unit' besar dalam kompleksitas - pertimbangkan untuk melakukan refactoring terhadap anggota pribadi seperti itu dari kelas ini.
Jika refactoring tidak sesuai atau tidak layak, dapatkah Anda menggunakan pola strategi untuk menggantikan akses ke fungsi anggota / kelas anggota privat ini saat diuji unit? Di bawah unit test, strategi akan memberikan validasi tambahan, tetapi dalam rilis rilis itu akan menjadi langkah sederhana.
sumber
Saya baru-baru ini memiliki masalah ini dan menulis sebuah alat kecil, yang disebut Picklock , yang menghindari masalah secara eksplisit menggunakan API refleksi Java, dua contoh:
Metode panggilan, misalnya
private void method(String s)
- dengan refleksi JavaMetode panggilan, misalnya
private void method(String s)
- oleh PicklockPengaturan bidang, misalnya
private BigInteger amount;
- oleh refleksi JawaPengaturan bidang, misalnya
private BigInteger amount;
- oleh Picklocksumber
PowerMockito dibuat untuk ini. Gunakan ketergantungan pakar
Maka Anda bisa melakukannya
sumber