Bagaimana Anda menguji metode pribadi?

184

Saya sedang mengerjakan proyek java. Saya baru dalam pengujian unit. Apa cara terbaik untuk menguji unit metode pribadi di kelas java?

Vinoth Kumar CM
sumber
1
Periksa pertanyaan ini di StackOverflow. Beberapa teknik disebutkan dan didiskusikan. Apa cara terbaik untuk menguji unit metode pribadi?
Chiron
34
Pendapat saya selalu bahwa metode pribadi tidak perlu pengujian karena Anda harus menguji apa yang tersedia. Metode publik. Jika Anda tidak dapat merusak metode publik, apakah itu benar-benar penting apa yang dilakukan metode pribadi?
Rig
1
Metode publik dan pribadi harus diuji. Oleh karena itu, test driver umumnya perlu berada di dalam kelas yang diuji. seperti ini .
pertukaran berlebihan
2
@Rig - +1 - Anda harus dapat memanggil semua perilaku yang diperlukan dari metode pribadi dari metode publik Anda - jika Anda tidak bisa maka fungsionalitasnya tidak akan pernah bisa dipanggil, jadi tidak ada gunanya mengujinya.
James Anderson
Gunakan @Jailbreakdari kerangka Manifold untuk secara langsung mengakses metode pribadi. Dengan cara ini kode tes Anda tetap aman dan mudah dibaca. Di atas segalanya, tidak ada kompromi desain, tidak ada metode dan bidang overexposing demi pengujian.
Scott

Jawaban:

239

Anda biasanya tidak menguji metode pribadi secara langsung. Karena bersifat pribadi, anggap mereka sebagai detail implementasi. Tidak ada yang akan memanggil salah satu dari mereka dan mengharapkannya bekerja dengan cara tertentu.

Anda sebaiknya menguji antarmuka publik Anda. Jika metode yang memanggil metode pribadi Anda berfungsi seperti yang Anda harapkan, Anda kemudian mengasumsikan dengan ekstensi bahwa metode pribadi Anda berfungsi dengan benar.

Adam Lear
sumber
39
+1 bazillion. Dan jika metode pribadi tidak pernah dipanggil, jangan unit-test itu, hapus!
Steven A. Lowe
246
Saya tidak setuju. Kadang-kadang metode pribadi hanyalah detail implementasi, tetapi masih cukup kompleks sehingga diperlukan pengujian, untuk memastikan itu berfungsi dengan baik. Antarmuka publik mungkin menawarkan tingkat abstraksi yang terlalu tinggi untuk menulis tes yang secara langsung menargetkan algoritma khusus ini. Tidak selalu layak untuk memfaktorkannya ke dalam kelas yang terpisah, karena karena data yang dibagikan. Dalam hal ini saya akan mengatakan tidak masalah untuk menguji metode pribadi.
quant_dev
10
Tentu saja hapus. Satu-satunya alasan yang mungkin untuk tidak menghapus fungsi yang tidak Anda gunakan adalah jika Anda berpikir Anda akan membutuhkannya di masa depan, dan bahkan kemudian Anda harus menghapusnya tetapi perhatikan fungsi di log komit Anda, atau setidaknya komentarlah sehingga orang tahu itu hal tambahan yang bisa mereka abaikan.
jhocking
38
@quant_dev: Kadang-kadang suatu kelas perlu dire-refored menjadi beberapa kelas lainnya. Data Anda yang dibagikan juga dapat ditarik ke kelas terpisah. Katakanlah, kelas "konteks". Kemudian dua kelas baru Anda dapat merujuk ke kelas konteks untuk data yang dibagikan. Ini mungkin tampak tidak masuk akal, tetapi jika metode pribadi Anda cukup kompleks untuk memerlukan pengujian individu, itu adalah bau kode yang menunjukkan grafik objek Anda harus menjadi sedikit lebih terperinci.
Phil
43
Jawaban ini TIDAK menjawab pertanyaan. Pertanyaannya adalah bagaimana metode pribadi harus diuji, bukan apakah mereka harus diuji. Apakah metode pribadi harus diuji unit adalah pertanyaan yang menarik dan layak diperdebatkan, tetapi tidak di sini . Respons yang sesuai adalah menambahkan komentar dalam pertanyaan yang menyatakan bahwa menguji metode pribadi mungkin bukan ide yang baik dan memberikan tautan ke pertanyaan terpisah yang akan membahas masalah ini lebih dalam.
TallGuy
118

Secara umum, saya akan menghindarinya. Jika metode pribadi Anda sangat kompleks sehingga perlu pengujian unit terpisah, itu sering berarti bahwa itu layak kelasnya sendiri. Ini dapat mendorong Anda untuk menulisnya dengan cara yang dapat digunakan kembali. Anda kemudian harus menguji kelas baru dan memanggil antarmuka publik di kelas lama Anda.

Di sisi lain, kadang-kadang memasukkan rincian implementasi ke dalam kelas terpisah mengarah ke kelas dengan antarmuka yang kompleks, banyak data yang lewat antara kelas lama dan baru, atau ke desain yang mungkin terlihat bagus dari sudut pandang OOP, tetapi tidak cocokkan dengan intuisi yang berasal dari domain masalah (mis. memecah model penetapan harga menjadi dua bagian hanya untuk menghindari pengujian metode pribadi yang tidak terlalu intuitif dan dapat menyebabkan masalah di kemudian hari ketika mempertahankan / memperluas kode). Anda tidak ingin memiliki "kelas kembar" yang selalu diubah bersama.

Ketika dihadapkan dengan pilihan antara enkapsulasi dan testabilitas, saya lebih memilih yang kedua. Lebih penting untuk memiliki kode yang benar (yaitu menghasilkan output yang benar) daripada desain OOP bagus yang tidak berfungsi dengan benar, karena tidak diuji secara memadai. Di Java, Anda cukup memberikan metode "default" akses dan meletakkan tes unit dalam paket yang sama. Tes unit hanyalah bagian dari paket yang Anda kembangkan, dan tidak apa-apa untuk memiliki ketergantungan antara tes dan kode yang sedang diuji. Ini berarti bahwa ketika Anda mengubah implementasi, Anda mungkin perlu mengubah tes Anda, tetapi tidak apa-apa - setiap perubahan implementasi memerlukan pengujian ulang kode, dan jika tes perlu dimodifikasi untuk melakukan itu, maka Anda cukup melakukan saya t.

Secara umum, sebuah kelas mungkin menawarkan lebih dari satu antarmuka. Ada antarmuka untuk pengguna, dan antarmuka untuk pengelola. Yang kedua dapat mengekspos lebih banyak untuk memastikan bahwa kode diuji secara memadai. Itu tidak harus menjadi unit test pada metode pribadi - itu bisa, misalnya, logging. Logging juga "merusak enkapsulasi", tetapi kami masih melakukannya, karena sangat berguna.

quant_dev
sumber
9
+1 Poin menarik. Pada akhirnya ini tentang pemeliharaan, bukan kepatuhan pada "aturan" OOP yang baik.
Phil
9
+1 Saya sepenuhnya setuju dengan testabilitas atas enkapsulasi.
Richard
31

Pengujian metode pribadi akan tergantung pada kompleksitasnya; beberapa metode privat satu baris tidak akan benar-benar menjamin upaya ekstra pengujian (ini juga dapat dikatakan sebagai metode publik), tetapi beberapa metode pribadi bisa sama rumitnya dengan metode publik, dan sulit untuk diuji melalui antarmuka publik.

Teknik pilihan saya adalah membuat paket metode privat menjadi privat, yang akan memungkinkan akses ke unit test dalam paket yang sama tetapi masih akan dienkapsulasi dari semua kode lainnya. Ini akan memberikan keuntungan dengan menguji logika metode pribadi secara langsung daripada harus bergantung pada uji metode publik untuk mencakup semua bagian (mungkin) logika kompleks.

Jika ini dipasangkan dengan anotasi @VisibleForTesting di pustaka Google Guava, Anda dengan jelas menandai metode pribadi paket ini sebagai terlihat hanya untuk pengujian dan karenanya, tidak boleh dipanggil oleh kelas lain.

Penentang teknik ini berpendapat bahwa ini akan memecah enkapsulasi dan membuka metode pribadi untuk kode dalam paket yang sama. Sementara saya setuju bahwa ini memecah enkapsulasi dan membuka kode privat ke kelas lain, saya berpendapat bahwa pengujian logika kompleks lebih penting daripada enkapsulasi ketat dan tidak menggunakan metode paket privat yang ditandai dengan jelas sebagai terlihat untuk pengujian hanya harus menjadi tanggung jawab pengembang. menggunakan dan mengubah basis kode.

Metode pribadi sebelum pengujian:

private int add(int a, int b){
    return a + b;
}

Paket metode pribadi siap untuk pengujian:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Catatan: Menempatkan tes dalam paket yang sama tidak setara dengan menempatkannya di folder fisik yang sama. Memisahkan kode utama dan kode uji Anda menjadi struktur folder fisik yang terpisah adalah praktik yang baik secara umum, tetapi teknik ini akan berfungsi selama kelas-kelas didefinisikan seperti dalam paket yang sama.

Richard
sumber
14

Jika Anda tidak dapat menggunakan API eksternal atau tidak mau, Anda masih dapat menggunakan API JDK standar murni untuk mengakses metode pribadi menggunakan refleksi. Berikut ini sebuah contoh

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Lihat Tutorial Java http://docs.oracle.com/javase/tutorial/reflect/ atau Java API http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html untuk informasi lebih lanjut.

Sebagai @ kij dikutip pada jawabannya ada kalanya solusi sederhana menggunakan refleksi benar-benar baik untuk menguji metode pribadi.

Gustavo Coelho
sumber
9

Unit test case berarti menguji unit kode. Itu tidak berarti menguji antarmuka karena jika Anda menguji antarmuka, itu tidak berarti Anda menguji unit kode. Itu menjadi semacam pengujian kotak hitam. Selain itu, lebih baik untuk menemukan masalah pada tingkat unit terkecil daripada menentukan masalah pada tingkat antarmuka dan kemudian mencoba men-debug bagian mana yang tidak berfungsi. Oleh karena itu, unit uji kasus harus diuji terlepas dari ruang lingkupnya. Mengikuti adalah cara untuk menguji metode pribadi.

Jika Anda menggunakan java, Anda bisa menggunakan jmockit yang menyediakan Deencapsulation.invoke untuk memanggil metode privat apa pun dari kelas yang sedang diuji. Ini menggunakan refleksi untuk menyebutnya pada akhirnya tetapi memberikan pembungkus yang bagus di sekitarnya. ( https://code.google.com/p/jmockit/ )

agrawalankur
sumber
bagaimana ini menjawab pertanyaan yang diajukan?
nyamuk
@gnat, Pertanyaannya adalah Apa cara terbaik untuk menguji unit metode pribadi di kelas java? Dan poin pertama saya menjawab pertanyaan itu.
agrawalankur
poin pertama Anda hanya mengiklankan alat, tetapi itu tidak menjelaskan mengapa Anda percaya itu lebih baik daripada alternatif, seperti misalnya menguji metode pribadi secara tidak langsung seperti yang disarankan dan dijelaskan dalam jawaban teratas
nyamuk
jmockit telah pindah dan halaman resmi yang lama menunjuk ke sebuah artikel tentang "Urine Sintetis dan Kencing Buatan". Ngomong-ngomong, Wich masih terkait dengan hal-hal yang mengejek, tetapi tidak relevan di sini :) Halaman resmi baru adalah: jmockit.github.io
Guillaume
8

Pertama-tama, seperti yang disarankan oleh penulis lain: pikirkan dua kali jika Anda benar-benar perlu menguji metode pribadi. Dan jika demikian, ...

Di .NET Anda dapat mengonversinya menjadi metode "Internal", dan membuat paket " InternalVisible " ke proyek unit test Anda.

Di Jawa Anda dapat menulis tes sendiri di kelas untuk diuji dan metode pengujian Anda harus dapat memanggil metode pribadi juga. Saya tidak benar-benar memiliki pengalaman Java yang besar, jadi itu mungkin bukan praktik terbaik.

Terima kasih.

Budda
sumber
7

Saya biasanya membuat metode seperti itu terlindungi. Katakanlah kelas Anda ada di:

src/main/java/you/Class.java

Anda dapat membuat kelas tes sebagai:

src/test/java/you/TestClass.java

Sekarang Anda memiliki akses ke metode yang dilindungi dan dapat unit mengujinya (JUnit atau TestNG tidak terlalu penting), namun Anda menyimpan metode ini dari penelepon yang tidak Anda inginkan.

Perhatikan bahwa ini mengharapkan sumber pohon gaya-maven.

gyorgyabraham
sumber
3

Jika Anda benar-benar perlu menguji metode pribadi, dengan Java yang saya maksud, Anda dapat menggunakan fest assertdan / atau fest reflect. Ini menggunakan refleksi.

Impor perpustakaan dengan pakar (versi yang diberikan bukan yang terbaru menurut saya) atau impor langsung di classpath Anda:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Sebagai contoh, jika Anda memiliki kelas bernama 'MyClass' dengan metode pribadi bernama 'myPrivateMethod' yang menggunakan String sebagai parameter, memperbarui nilainya ke 'this is cool testing!', Anda dapat melakukan tes junit berikut:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Perpustakaan ini juga memungkinkan Anda untuk mengganti properti kacang apa pun (tidak peduli mereka bersifat pribadi dan tidak ada setter yang ditulis) oleh tiruan, dan menggunakan ini dengan Mockito atau kerangka tiruan lainnya benar-benar keren. Satu-satunya hal yang harus Anda ketahui saat ini (tidak tahu apakah ini akan lebih baik di versi berikutnya) adalah nama bidang / metode target yang ingin Anda manipulasi, dan tanda tangannya.

kij
sumber
2
Sementara refleksi akan memungkinkan Anda untuk menguji metode pribadi, itu tidak baik untuk mendeteksi penggunaannya. Jika Anda mengubah nama metode pribadi, ini akan merusak pengujian Anda.
Richard
1
Tes akan pecah ya, tapi ini adalah masalah kecil dari sudut pandang saya. Tentu saja saya sepenuhnya setuju tentang penjelasan Anda di bawah ini pada visibilitas paket (dengan kelas tes dalam folder terpisah tetapi dengan paket yang sama), tetapi kadang-kadang Anda tidak benar-benar punya pilihan. Misalnya jika Anda memiliki misi yang sangat singkat di perusahaan yang tidak menerapkan metode "baik" (kelas uji tidak dalam paket yang sama, dll.), Jika Anda tidak punya waktu untuk memperbaiki kode yang ada di mana Anda bekerja / pengujian, ini masih merupakan alternatif yang baik.
kij
2

Apa yang biasanya saya lakukan di C # adalah membuat metode saya terlindungi dan tidak pribadi. Ini sedikit pengubah akses pribadi, tetapi menyembunyikan metode dari semua kelas yang tidak diwariskan dari kelas yang diuji.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Setiap kelas yang tidak mewarisi langsung dari classUnderTest tidak tahu bahwa methodToTest bahkan ada. Dalam kode pengujian saya, saya dapat membuat kelas pengujian khusus yang memperluas dan menyediakan akses ke metode ini ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Kelas ini hanya ada di proyek pengujian saya. Tujuan utamanya adalah untuk menyediakan akses ke metode tunggal ini. Ini memungkinkan saya untuk mengakses tempat-tempat yang tidak dimiliki sebagian besar kelas lain.

angkasawan
sumber
4
protectedadalah bagian dari API publik dan menimbulkan batasan yang sama (tidak pernah dapat diubah, harus didokumentasikan, ...). Di C # Anda bisa menggunakannya internalbersama InternalsVisibleTo.
CodesInChaos
1

Anda dapat dengan mudah menguji metode pribadi jika Anda menempatkan unit test Anda di kelas dalam pada kelas yang Anda uji. Menggunakan TestNG tes unit Anda harus kelas internal statis publik yang dijelaskan dengan @Test, seperti ini:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Karena ini adalah kelas dalam, metode pribadi dapat dipanggil.

Tes saya dijalankan dari pakar dan secara otomatis menemukan kasus uji ini. Jika Anda hanya ingin menguji satu kelas yang dapat Anda lakukan

$ mvn test -Dtest=*UserEditorTest

Sumber: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Roger Keays
sumber
4
Anda menyarankan memasukkan kode tes (dan kelas dalam tambahan) dalam kode aktual paket yang digunakan?
1
Kelas tes adalah kelas dalam dari kelas yang ingin Anda uji. Jika Anda tidak ingin kelas-kelas dalam digunakan, Anda bisa menghapus file * Test.class dari paket Anda.
Roger Keays
1
Saya tidak suka opsi ini, tetapi bagi banyak orang itu mungkin solusi yang valid, tidak sepadan dengan downvotes.
Bill K
@RogerKeays: Secara teknis saat Anda melakukan penempatan, bagaimana Anda menghapus "kelas dalam uji" tersebut? Tolong jangan katakan Anda memotongnya dengan tangan.
gyorgyabraham
Saya tidak menghapusnya. Iya. Saya menggunakan kode yang tidak pernah dieksekusi saat dijalankan. Seburuk itu.
Roger Keays