Bagaimana cara menguji fungsi pribadi atau kelas yang memiliki metode pribadi, bidang atau kelas dalam?

2728

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 ( staticdalam C / C ++) atau dalam namespace pribadi ( anonim )?

Tampaknya buruk untuk mengubah pengubah akses untuk suatu metode atau fungsi hanya untuk dapat menjalankan tes.

Raedwald
sumber
257
Cara terbaik untuk menguji metode pribadi adalah tidak mengujinya secara langsung
Surya
26
Periksa artikel Menguji Metode Pribadi dengan JUnit dan SuiteRunner .
Mouna Cheikhna
383
Saya tidak setuju. Metode (publik) yang panjang atau sulit untuk dipahami harus di refactored. Adalah bodoh untuk tidak menguji metode kecil (pribadi) yang Anda dapatkan alih-alih hanya metode umum.
Michael Piefel
181
Tidak menguji metode apa pun hanya karena visibilitasnya bodoh. Bahkan unit test harus mengenai potongan kode terkecil, dan jika Anda hanya menguji metode publik, Anda tidak akan pernah sekarang yakin di mana kesalahan terjadi - metode itu, atau yang lainnya.
Dainius
126
Anda perlu menguji fungsionalitas kelas , bukan implementasinya . Ingin menguji metode pribadi? Uji metode publik yang memanggil mereka. Jika fungsionalitas yang ditawarkan kelas diuji secara menyeluruh, bagian dalamnya telah terbukti benar dan dapat diandalkan; Anda tidak perlu menguji kondisi internal. Tes harus mempertahankan decoupling dari kelas yang diuji.
Calimar41

Jawaban:

1640

Memperbarui:

Beberapa 10 tahun kemudian mungkin cara terbaik untuk menguji metode pribadi, atau anggota tidak dapat diakses, adalah melalui @Jailbreakdari Manifold kerangka.

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

Dengan cara ini kode Anda tetap aman dan mudah dibaca. Tidak ada kompromi desain, tidak ada metode dan bidang overexposing demi pengujian.

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 privatedan private staticvariabel serta memohon privatedan private staticmetode. Pola-pola berikut akan memungkinkan Anda melakukan hampir semua hal yang berhubungan dengan metode dan bidang pribadi. Tentu saja, Anda tidak dapat mengubah private static finalvariabel melalui refleksi.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

Dan untuk bidang:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Catatan:
1. TargetClass.getDeclaredMethod(methodName, argClasses)memungkinkan Anda melihat privatemetode. Hal yang sama berlaku untuk getDeclaredField.
2. setAccessible(true)Diperlukan untuk bermain-main dengan prajurit.

Cem Catikkas
sumber
350
Berguna jika Anda tidak tahu API mungkin, tetapi jika Anda harus menguji metode pribadi dengan cara ini ada sesuatu dengan desain Anda. Seperti poster lain mengatakan pengujian unit harus menguji kontrak kelas: jika kontrak itu terlalu luas dan terlalu banyak instantiates sistem maka desain harus ditangani.
andygavin
21
Sangat berguna. Menggunakan ini penting untuk diingat bahwa itu akan gagal jika tes dijalankan setelah kebingungan.
Rick Minerich
20
Contoh kode tidak berfungsi untuk saya, tetapi ini membuat thig
Rob
35
Jauh lebih baik daripada menggunakan refleksi secara langsung dengan menggunakan beberapa perpustakaan untuk itu seperti Powermock .
Michael Piefel
25
Inilah sebabnya mengapa Test Driven Design sangat membantu. Ini membantu Anda mengetahui apa yang perlu diekspos untuk memvalidasi perilaku.
Thorbjørn Ravn Andersen
621

Cara terbaik untuk menguji metode pribadi adalah melalui metode publik lainnya. Jika ini tidak dapat dilakukan, maka salah satu dari kondisi berikut ini benar:

  1. Metode pribadi adalah kode mati
  2. Ada bau desain di dekat kelas yang Anda uji
  3. Metode yang Anda coba uji tidak boleh bersifat pribadi
Trumpi
sumber
6
Lebih lanjut, kami tidak pernah mendekati cakupan kode 100%, jadi mengapa tidak memfokuskan waktu Anda melakukan pengujian kualitas pada metode yang sebenarnya akan digunakan klien secara langsung.
Grinch
30
@grinch Spot aktif. Satu-satunya hal yang akan Anda peroleh dari pengujian metode pribadi adalah informasi debug, dan untuk itulah para debugger. Jika tes Anda dari kontrak kelas memiliki cakupan penuh maka Anda memiliki semua informasi yang Anda butuhkan. Metode pribadi adalah detail implementasi . Jika Anda menguji mereka, Anda harus mengubah tes Anda setiap kali implementasi Anda berubah, bahkan jika kontrak tidak. Dalam siklus hidup penuh dari perangkat lunak ini cenderung lebih mahal daripada manfaatnya.
Erik Madsen
9
@ AlexWien Anda benar. Cakupan bukan istilah yang tepat. Apa yang seharusnya saya katakan adalah "jika pengujian Anda terhadap kontrak kelas mencakup semua input yang berarti dan semua status yang bermakna". Ini, Anda tunjukkan dengan benar, mungkin terbukti tidak layak untuk diuji melalui antarmuka publik ke kode, atau secara tidak langsung saat Anda merujuknya. Jika itu adalah kasus untuk beberapa kode yang Anda uji, saya akan cenderung berpikir: (1) Kontrak terlalu murah (mis. Terlalu banyak parameter dalam metode), atau (2) Kontrak terlalu kabur (misalnya perilaku metode) sangat bervariasi dengan keadaan kelas). Saya akan mempertimbangkan kedua aroma desain.
Erik Madsen
16
Hampir semua metode pribadi tidak boleh diuji secara langsung (untuk mengurangi biaya perawatan). Pengecualian: Metode ilmiah mungkin memiliki bentuk seperti f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) di mana a, b, c, d, e, dan f adalah ekspresi yang sangat rumit tetapi sebaliknya tidak berguna di luar rumus tunggal ini. Beberapa fungsi (pikir MD5) sulit untuk dibalik sehingga bisa jadi sulit (NP-Hard) untuk memilih parameter yang sepenuhnya akan mencakup ruang perilaku. Lebih mudah untuk menguji a, b, c, d, e, f secara independen. Menjadikannya kebohongan publik atau paket bahwa itu dapat digunakan kembali. Solusi: jadikan itu pribadi, tetapi ujilah.
Eponim
4
@Balas, saya agak sobek pada yang ini. Purist berorientasi objek pada saya akan mengatakan Anda harus menggunakan pendekatan berorientasi objek daripada metode pribadi statis, yang memungkinkan Anda untuk menguji melalui metode contoh publik. Tetapi sekali lagi, dalam kerangka kerja ilmiah, memori kemungkinan besar menjadi masalah yang saya yakini mendukung pendekatan statis.
Erik Madsen
323

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.

Jay Bazuzi
sumber
24
Pertanyaannya adalah bagaimana menguji metode pribadi. Anda mengatakan bahwa Anda akan melakukan kelas baru untuk itu (dan menambahkan lebih banyak kerumitan) dan setelah menyarankan untuk tidak membuat kelas baru. Jadi, bagaimana menguji metode pribadi?
Dainius
27
@ Dainius: Saya tidak menyarankan membuat kelas baru semata-mata sehingga Anda dapat menguji metode itu. Saya menyarankan agar tes menulis dapat membantu Anda meningkatkan desain Anda: desain yang baik mudah untuk diuji.
Jay Bazuzi
13
Tetapi Anda setuju bahwa OOD yang baik hanya akan memaparkan (mempublikasikan) metode yang diperlukan agar kelas / objek tersebut bekerja dengan benar? semua yang lain harus pribadi / protektif. Jadi dalam beberapa metode pribadi akan ada beberapa logika, dan pengujian IMO metode ini hanya akan meningkatkan kualitas perangkat lunak. Tentu saja saya setuju bahwa jika beberapa kode menjadi kompleks, maka harus dibagi ke metode / kelas yang terpisah.
Dainius
6
@Danius: Menambahkan kelas baru, dalam banyak kasus mengurangi kompleksitas. Ukur saja. Testabilitas dalam beberapa kasus berkelahi melawan OOD. Pendekatan modern adalah mendukung testability.
AlexWien
5
Inilah cara tepatnya menggunakan umpan balik dari tes Anda untuk mendorong dan meningkatkan desain Anda! Dengarkan tes Anda. jika mereka sulit untuk menulis, itu memberi tahu Anda sesuatu yang penting tentang kode Anda!
pnschofield
289

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.

Jon
sumber
76
Beri +1 pada ini. Menurut saya itu adalah jawaban terbaik untuk pertanyaan itu. Dengan menguji metode pribadi Anda menguji implementasi. Ini mengalahkan tujuan pengujian unit, yaitu untuk menguji input / output kontrak kelas. Sebuah tes seharusnya hanya cukup tahu tentang implementasi untuk mengejek metode yang dipanggilnya pada dependensinya. Tidak ada lagi. Jika Anda tidak dapat mengubah implementasi Anda tanpa harus mengubah tes - kemungkinan strategi pengujian Anda buruk.
Colin M
10
@Colin M Bukan itu yang dia tanyakan;) Biarkan dia yang memutuskan, Anda tidak tahu proyeknya.
Aaron Marcus
2
Tidak sepenuhnya benar. Diperlukan banyak upaya untuk menguji sebagian kecil dari metode pribadi melalui metode publik yang menggunakannya. Metode publik dapat memerlukan pengaturan yang signifikan sebelum Anda mencapai garis yang memanggil metode pribadi Anda
ACV
1
Saya setuju. Anda harus menguji, jika kontrak terpenuhi. Anda tidak boleh menguji, bagaimana ini dilakukan. Jika kontak terpenuhi tanpa mencapai cakupan kode 100% (dalam metode pribadi), itu mungkin kode mati atau tidak berguna.
wuppi
207

Dari artikel ini: Menguji Metode Pribadi dengan JUnit dan SuiteRunner (Bill Venners), Anda pada dasarnya memiliki 4 opsi:

  1. Jangan menguji metode pribadi.
  2. Berikan metode akses paket.
  3. Gunakan kelas tes bersarang.
  4. Gunakan refleksi.
Iker Jimenez
sumber
@ JimmyT., Tergantung pada siapa "kode produksi" untuk. Saya akan memanggil kode yang dihasilkan untuk aplikasi yang ditempatkan di dalam VPN yang target pengguna adalah sysadmin menjadi kode produksi.
Pacerier
@Pacerier Apa maksudmu?
Jimmy T.
1
Opsi 5, seperti yang disebutkan di atas adalah menguji metode publik yang memanggil metode pribadi? Benar?
user2441441
1
@LorenzoLerate Saya telah bergulat dengan ini karena saya sedang melakukan pengembangan tertanam dan saya ingin perangkat lunak saya menjadi sekecil mungkin. Kendala ukuran dan kinerja adalah satu-satunya alasan yang dapat saya pikirkan.
Saya sangat suka tes menggunakan refleksi: Di ​​sini pola Metode metode = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return method.invoke (targetObject, argObjects);
Aguid
127

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.

John Channing
sumber
16
Itulah jawaban terbaik IMO, atau seperti yang biasa dikatakan, menguji perilaku, bukan metode. Pengujian unit bukan pengganti metrik kode sumber, alat analisis kode statis, dan ulasan kode. Jika metode pribadi sangat kompleks sehingga mereka perlu memisahkan tes maka mungkin perlu direaktor ulang, tidak lebih banyak tes dilemparkan ke sana.
Dan Haynes
14
jadi jangan menulis metode pribadi, cukup buat 10 kelas kecil lainnya?
pisau cukur
1
Tidak, itu pada dasarnya berarti Anda dapat menguji fungsionalitas metode pribadi menggunakan metode publik yang memanggilnya.
Akshat Sharda
67

Hanya dua contoh di mana saya ingin menguji metode pribadi:

  1. Rutin dekripsi - Saya tidak ingin membuatnya terlihat oleh siapa pun hanya untuk kepentingan pengujian, kalau tidak, siapa pun dapat menggunakannya untuk mendekripsi. Tetapi mereka intrinsik dengan kode, rumit, dan perlu selalu bekerja (pengecualian yang jelas adalah refleksi yang dapat digunakan untuk melihat bahkan metode pribadi dalam banyak kasus, ketikaSecurityManager tidak dikonfigurasi untuk mencegah hal ini).
  2. Membuat SDK untuk konsumsi masyarakat. Di sini publik mengambil makna yang sepenuhnya berbeda, karena ini adalah kode yang dapat dilihat seluruh dunia (bukan hanya internal aplikasi saya). Saya memasukkan kode ke metode pribadi jika saya tidak ingin pengguna SDK melihatnya - saya tidak melihat ini sebagai kode bau, hanya seperti cara kerja pemrograman SDK. Tapi tentu saja saya masih perlu menguji metode pribadi saya, dan mereka di mana fungsi SDK saya benar-benar hidup.

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.

Richard Le Mesurier
sumber
5
Meskipun Anda tidak boleh membuat metode publik hanya untuk mengujinya, privatebukan satu-satunya alternatif. Tidak ada pengubah akses yang package privateberarti Anda dapat mengujinya selama pengujian unit Anda berlangsung dalam paket yang sama.
ngreen
1
Saya mengomentari jawaban Anda, titik 1 khususnya, bukan OP. Tidak perlu membuat metode pribadi hanya karena Anda tidak ingin itu menjadi publik.
ngreen
1
@ngreen true thx - Saya malas dengan kata "publik". Saya telah memperbarui jawaban untuk menyertakan publik, dilindungi, default (dan untuk menyebutkan refleksi). Poin yang saya coba sampaikan adalah bahwa ada alasan bagus untuk beberapa kode menjadi rahasia, namun itu seharusnya tidak mencegah kita untuk mengujinya.
Richard Le Mesurier
2
Terima kasih telah memberikan contoh nyata kepada semua orang. Sebagian besar jawaban baik, tetapi dalam sudut pandang teori. Di dunia nyata, kita perlu menyembunyikan implementasi dari kontrak, dan kita masih perlu mengujinya. Membaca semua ini, saya pikir mungkin ini harus menjadi tingkat pengujian yang sama sekali baru, dengan nama yang berbeda
Z. Khullah
1
Satu lagi contoh - Pengurai HTML crawler . Harus mem-parsing seluruh halaman html ke dalam objek domain membutuhkan banyak logika seluk-beluk memvalidasi bagian-bagian kecil dari struktur. Masuk akal untuk memecahnya menjadi metode dan menyebutnya dari satu metode publik, namun tidak masuk akal untuk memiliki semua metode yang lebih kecil yang membuatnya menjadi publik, juga tidak masuk akal untuk membuat 10 kelas per halaman HTML.
Nether
59

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.

Thomas Owens
sumber
Fakta sebenarnya. Masalahnya adalah ketika implementasi lebih rumit, seperti jika satu metode publik memanggil beberapa metode pribadi. Yang mana yang gagal? Atau jika ini adalah metode pribadi yang rumit, yang tidak dapat diekspos atau dipindahkan ke kelas baru
Z. Khullah
Anda sudah menyebutkan mengapa metode pribadi harus diuji. Anda mengatakan "kalau begitu bisa jadi", jadi Anda tidak yakin. IMO, apakah suatu metode harus diuji adalah ortogonal ke tingkat aksesnya.
Wei Qiu
39

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.javamaka panggilan uji Anda harus dimasukkan src/test/java/mypackage/MyClassTest.java. Dengan begitu, Anda mendapat akses ke metode tes di kelas tes Anda.

Peter Mortensen
sumber
1
Saya tidak tahu tentang yang ini, itu interresting, saya masih berpikir bahwa jika Anda membutuhkan semacam annottaion Anda memiliki masalah desain.
Seb
2
Bagi saya, ini seperti mengatakan alih-alih memberikan kunci satu kali ke petugas pemadam kebakaran untuk menguji latihan kebakaran, Anda membiarkan pintu depan Anda tidak terkunci dengan tanda di pintu yang mengatakan - "tidak terkunci untuk pengujian - jika Anda bukan dari perusahaan kami, tolong jangan masuk ".
Pater Jeremy Krieg
@FrJeremyKrieg - apa artinya itu?
MasterJoe2
1
@ MasterJoe2: tujuan dari tes kebakaran adalah untuk meningkatkan keamanan bangunan Anda terhadap risiko kebakaran. Tetapi Anda harus memberi sipir akses ke gedung Anda. Jika Anda membiarkan pintu depan tidak terkunci secara permanen, Anda meningkatkan risiko akses yang tidak sah dan membuatnya kurang aman. Hal yang sama berlaku untuk pola VisibleForTesting - Anda meningkatkan risiko akses yang tidak sah untuk mengurangi risiko "kebakaran". Lebih baik memberikan akses sebagai sekali saja untuk pengujian hanya ketika Anda perlu (misalnya, menggunakan refleksi) daripada membiarkannya tidak terkunci secara permanen (non-pribadi).
Pater Jeremy Krieg
34

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.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
phareim
sumber
2
Pastikan untuk mengunduh seluruh paket JUnit-addons ( sourceforge.net/projects/junit-addons ), bukan Project Private Forge-Recommended Project Forge-Recommended.
skia.heliou
2
Dan pastikan bahwa, ketika Anda menggunakan Classparameter pertama Anda dalam metode ini, Anda hanya mengakses staticanggota.
skia.heliou
31

Dalam Kerangka Kerja Spring Anda dapat menguji metode pribadi menggunakan metode ini:

ReflectionTestUtils.invokeMethod()

Sebagai contoh:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
Mikhail
sumber
Solusi terbersih dan paling ringkas sejauh ini, tetapi hanya jika Anda menggunakan Spring.
Denis Nikolaenko
28

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:

  • Dapat menguji granularity yang lebih halus

Kekurangan:

  • Kode uji harus berada dalam file yang sama dengan kode sumber, yang bisa lebih sulit untuk dipelihara
  • Demikian pula dengan file output .class, mereka harus tetap dalam paket yang sama seperti yang dinyatakan dalam kode sumber

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:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

Kelas dalam akan dikompilasi ClassToTest$StaticInnerTest .

Lihat juga: Java Tip 106: Kelas dalam statis untuk kesenangan dan keuntungan

Grundlefleck
sumber
26

Seperti yang orang lain katakan ... jangan menguji metode pribadi secara langsung. Berikut ini beberapa pemikiran:

  1. Jaga agar semua metode tetap kecil dan fokus (mudah diuji, mudah menemukan apa yang salah)
  2. Gunakan alat cakupan kode. Saya suka Cobertura (oh selamat hari, sepertinya versi baru sudah keluar!)

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.

TofuBeer
sumber
Untuk cakupan kode. Apa pun jenis logika yang digunakan dalam metode pribadi, Anda masih menggunakan logika itu melalui metode publik. Alat cakupan kode dapat menunjukkan kepada Anda bagian mana yang dicakup oleh tes, karena itu Anda dapat melihat apakah metode pribadi Anda diuji.
coolcfan
Saya telah melihat kelas di mana satu-satunya metode publik adalah utama [], dan mereka muncul GUI plus menghubungkan database dan beberapa server web di seluruh dunia. Mudah dikatakan "jangan tes secara tidak langsung" ...
Audrius Meskauskas
26

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.

Antony Booth
sumber
2
Walaupun ini benar, ini dapat mengarah pada beberapa tes yang sangat rumit - ini adalah pola yang lebih baik untuk menguji satu metode pada satu waktu (Pengujian unit) daripada seluruh kelompok dari mereka. Bukan saran yang buruk tapi saya pikir ada jawaban yang lebih baik di sini - ini harus menjadi pilihan terakhir.
Bill K
24

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:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
Steve Chambers
sumber
24

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.

Don Kirkby
sumber
19

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.

Peter Mortensen
sumber
4
unit test dan kode src adalah pasangan. Jika Anda mengubah src, mungkin Anda harus mengubah tes unit. Itulah arti dari tes junit. Mereka akan garuantee bahwa semua berfungsi seperti sebelumnya. dan itu baik-baik saja jika mereka rusak, jika Anda mengubah kode.
AlexWien
1
Jangan setuju bahwa uji unit harus berubah jika kode berubah. Jika Anda diperintahkan untuk menambahkan fungsionalitas ke kelas tanpa mengubah fungsionalitas kelas yang ada, maka tes unit yang ada harus lulus bahkan setelah Anda mengubah kode Anda. Seperti yang disebutkan Peter, unit test harus menguji antarmuka, bukan bagaimana hal itu dilakukan secara internal. Dalam Tes Didorong Pengembangan unit tes dibuat sebelum kode ditulis, untuk fokus pada antarmuka kelas, bukan pada bagaimana hal itu diselesaikan secara internal.
Harald Coppoolse
16

Jawaban dari halaman FAQ JUnit.org :

Tetapi jika Anda harus ...

Jika Anda menggunakan JDK 1.3 atau lebih tinggi, Anda dapat menggunakan refleksi untuk menumbangkan mekanisme kontrol akses dengan bantuan PrivilegedAccessor . Untuk detail tentang cara menggunakannya, baca artikel ini .

Jika Anda menggunakan JDK 1.6 atau lebih tinggi dan Anda membuat anotasi tes dengan @Test, Anda dapat menggunakan Dp4j untuk menyuntikkan refleksi dalam metode pengujian Anda. Untuk detail tentang cara menggunakannya, lihat skrip pengujian ini .

PS Saya kontributor utama untuk Dp4j , tanyakan kepada saya jika Anda membutuhkan bantuan. :)

simpatico
sumber
16

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.

Akan Sargent
sumber
4
Sebagai bagian dari perpustakaan jmockit, Anda memiliki akses ke kelas Deencapsulation yang membuat pengujian metode pribadi mudah: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.
Saya menggunakan pendekatan ini sepanjang waktu. Sangat membantu
MedicineMan
14

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).

bmurphy1976
sumber
14

Jika Anda menggunakan JUnit, lihatlah junit-addons . Ini memiliki kemampuan untuk mengabaikan model keamanan Java dan mengakses metode dan atribut pribadi.

Joe
sumber
13

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:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Saya akan menggunakan ini:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

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 Callabledan kemudian Anda memasukkan antarmuka itu juga dalam konstruktor (dengan beberapa trik konstruktor):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

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.

GROX13
sumber
1
Tidak setuju dengan ClassToTest(Callable)suntikan. Itu membuat ClassToTestlebih rumit. Tetap sederhana. Juga, itu kemudian mengharuskan orang lain untuk mengatakan ClassToTestsesuatu yang ClassToTestseharusnya menjadi bos. Ada tempat untuk menyuntikkan logika, tetapi ini bukan. Anda baru saja membuat kelas lebih sulit untuk dipertahankan, tidak mudah.
Loduwijk
Juga, lebih disukai jika metode pengujian XAnda tidak meningkatkan Xkompleksitas 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)
Loduwijk
2
Bisakah Anda menjelaskan mengapa hal itu membuat kelas ClassToTestlebih 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 perlukan firstdan variabel 'kedua'?
GROX13
1
Metode pengujian tidak meningkatkan kompleksitas. Hanya kelas Anda yang ditulis dengan buruk, begitu buruk sehingga tidak dapat diuji.
GROX13
Sulit dipertahankan karena kelasnya lebih rumit. class A{ A(){} f(){} }lebih sederhana dari class 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 mengklaim class 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"
Loduwijk
12

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.

loknath
sumber
Anda mencampur "dilindungi" dengan "ramah." Metode yang dilindungi hanya dapat diakses oleh kelas yang objeknya dapat ditetapkan ke kelas target (yaitu subclass).
Sayo Oladeji
1
Sebenarnya tidak ada "Ramah" di Jawa, istilahnya adalah "Paket" dan itu ditunjukkan oleh kurangnya pengubah pribadi / publik / dilindungi. Saya baru saja mengoreksi jawaban ini tetapi ada yang benar-benar bagus yang sudah mengatakan ini - jadi saya hanya merekomendasikan menghapusnya.
Bill K
Saya hampir kalah suara, berpikir selama ini, "Jawaban ini benar-benar salah." sampai saya sampai pada kalimat terakhir, yang bertentangan dengan sisa jawabannya. Kalimat terakhir seharusnya menjadi yang pertama.
Loduwijk
11

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.

Darren Greaves
sumber
Anda seharusnya tidak menguji secara tidak langsung! Tidak hanya menyentuh melalui liputan; menguji bahwa hasil yang diharapkan disampaikan!
AlexWien
11

Inilah fungsi generik saya untuk menguji bidang pribadi:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}
Yuli Reiri
sumber
10

Silakan lihat di bawah untuk contoh;

Pernyataan impor berikut harus ditambahkan:

import org.powermock.reflect.Whitebox;

Sekarang Anda dapat langsung melewati objek yang memiliki metode pribadi, nama metode untuk dipanggil, dan parameter tambahan seperti di bawah ini.

Whitebox.invokeMethod(obj, "privateMethod", "param1");
Olcay Tarazan
sumber
10

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:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

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

Snicolas
sumber
1
Baru saja mencobanya, BoundBox adalah solusi yang simpel dan elegan!
forhas
9

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.

Aaron
sumber
3
Karena seringkali sepotong kode tertentu dari metode publik di refactored menjadi metode privat internal dan benar-benar potongan kritis logika yang Anda mungkin salah. Anda ingin menguji ini secara independen dari metode publik
oxbow_lakes
Bahkan kode terpendek terkadang tanpa unit test tidak benar. Cobalah untuk menghitung perbedaan antara 2 sudut geograhpikal. 4 baris kode, dan sebagian besar tidak akan melakukannya dengan benar pada percobaan pertama. Metode seperti ini membutuhkan pengujian unit, karena merupakan dasar dari kode yang dapat dipercaya. (Ans kode yang berguna seperti itu bisa bersifat publik, juga kurang dilindungi
AlexWien
8

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 Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Metode panggilan, misalnya private void method(String s)- oleh Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Pengaturan bidang, misalnya private BigInteger amount;- oleh refleksi Jawa

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Pengaturan bidang, misalnya private BigInteger amount;- oleh Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));
CoronA
sumber
7

PowerMockito dibuat untuk ini. Gunakan ketergantungan pakar

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Maka Anda bisa melakukannya

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
Victor Grazi
sumber