Membuat metode pribadi publik untuk unit mengujinya ... ide bagus?

301

Moderator Catatan: Sudah ada 39 jawaban yang diposting di sini (beberapa telah dihapus). Sebelum Anda memposting jawaban Anda , pertimbangkan apakah Anda dapat menambahkan sesuatu yang bermakna atau tidak untuk diskusi. Kemungkinan besar Anda hanya mengulangi apa yang sudah dikatakan orang lain.


Saya kadang-kadang menemukan diri saya perlu membuat metode pribadi di depan umum hanya untuk menulis beberapa tes unit untuk itu.

Biasanya ini terjadi karena metode ini berisi logika yang dibagi antara metode lain di kelas dan itu lebih rapi untuk menguji logika sendiri, atau alasan lain dapat dimungkinkan karena saya ingin menguji logika yang digunakan dalam utas sinkron tanpa harus khawatir tentang masalah threading .

Apakah orang lain mendapati diri mereka melakukan ini, karena saya tidak begitu suka melakukannya ?? Saya pribadi berpikir bonus lebih besar daripada masalah membuat metode publik yang tidak benar-benar menyediakan layanan di luar kelas ...

MEMPERBARUI

Terima kasih atas jawaban semua orang, tampaknya telah menarik minat masyarakat. Saya pikir konsensus umum pengujian harus terjadi melalui API publik karena ini adalah satu-satunya cara kelas akan pernah digunakan, dan saya setuju dengan ini. Beberapa kasus yang saya sebutkan di atas di mana saya akan melakukan ini di atas adalah kasus yang tidak biasa dan saya pikir manfaat melakukan itu sepadan.

Namun saya bisa, melihat setiap titik bahwa itu seharusnya tidak pernah benar-benar terjadi. Dan ketika memikirkannya sedikit lagi saya pikir mengubah kode Anda untuk mengakomodasi tes adalah ide yang buruk - setelah semua saya kira pengujian adalah alat pendukung dengan cara dan mengubah sistem untuk 'mendukung alat pendukung' jika Anda mau, adalah terang-terangan latihan yang buruk.

jcvandan
sumber
2
"Mengubah sistem untuk 'mendukung alat pendukung' jika Anda mau, adalah praktik buruk yang mencolok". Ya, semacam. Tetapi OTOH jika sistem Anda tidak bekerja dengan baik dengan alat yang ada, mungkin ada yang salah dengan sistem.
Thilo
1
Bagaimana ini bukan duplikat dari stackoverflow.com/questions/105007/do-you-test-private-method ?
Sanghyun Lee
1
Bukan jawaban, tetapi Anda selalu dapat mengaksesnya dengan refleksi.
Zano
33
Tidak, Anda seharusnya tidak mengekspos mereka. Sebagai gantinya, Anda harus menguji bahwa kelas Anda berlaku seperti seharusnya, melalui API publiknya. Dan jika mengekspos internal benar-benar satu-satunya pilihan (yang saya ragu), maka Anda setidaknya harus membuat paket accessors terlindungi, sehingga hanya kelas dalam paket yang sama (seperti tes Anda seharusnya) yang dapat mengaksesnya.
JB Nizet
4
Ya itu. Sangat dapat diterima untuk memberikan visibilitas paket-privat metode untuk membuatnya dapat diakses dari unit test. Perpustakaan seperti Guava bahkan memberikan @VisibileForTestinganotasi untuk membuat metode seperti itu - saya sarankan Anda melakukan ini sehingga alasan metode ini tidak privatedidokumentasikan dengan baik.
Boris the Spider

Jawaban:

186

Catatan:
Jawaban ini awalnya diposting untuk pertanyaan Apakah pengujian unit saja pernah menjadi alasan yang bagus untuk mengekspos variabel instance pribadi melalui getter? yang digabung menjadi yang satu ini, jadi ini mungkin sedikit khusus untuk usecase yang disajikan di sana.

Sebagai pernyataan umum, saya biasanya menggunakan kode "produksi" refactoring untuk membuatnya lebih mudah untuk diuji. Namun, saya tidak berpikir itu akan menjadi panggilan yang bagus di sini. Tes unit yang baik (biasanya) seharusnya tidak peduli dengan detail implementasi kelas, hanya tentang perilaku yang terlihat. Alih-alih mengekspos tumpukan internal untuk tes, Anda dapat menguji bahwa kelas mengembalikan halaman dalam urutan yang Anda harapkan setelah menelepon first()atau last().

Sebagai contoh, pertimbangkan kode-pseudo ini:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}
Mureinik
sumber
8
Saya sepenuhnya setuju dengan jawaban Anda. Banyak pengembang akan tergoda untuk menggunakan pengubah akses default dan membenarkan bahwa kerangka kerja open source yang baik menggunakan strategi ini sehingga harus benar. Hanya karena Anda tidak dapat berarti Anda harus melakukannya. +1
CKing
6
Meskipun orang lain telah memberikan wawasan yang bermanfaat, saya harus mengatakan ini adalah solusi yang saya anggap paling tepat untuk masalah ini. Itu membuat tes sepenuhnya agnostik untuk bagaimana perilaku yang diinginkan dicapai secara internal. +10!
Gitahi Ng'ang'a
Ngomong-ngomong, apakah tidak apa-apa bahwa dalam contoh yang diberikan oleh @Mureinik di sini, metode pengujian pada akhirnya mencakup lebih dari metode aktual yang diuji? Misalnya, testFirst () memanggil next (), yang sebenarnya bukan metode yang diuji di sini, first () adalah. Bukankah lebih bersih dan jelas jika hanya memiliki 1 metode pengujian yang mencakup semua 4 metode navigasi?
Gitahi Ng'ang'a
1
Meskipun ini adalah yang ideal, ada kalanya Anda perlu memeriksa komponen melakukan sesuatu dengan cara yang benar bukan hanya itu berhasil. Ini bisa lebih sulit untuk tes kotak hitam. Kadang-kadang Anda harus menguji kotak putih komponen.
Peter Lawrey
1
Membuat getter hanya demi pengujian unit? Ini jelas bertentangan dengan butir OOP / Object Thinking karena antarmuka harus mengekspos perilaku, bukan data.
IntelliData
151

Secara pribadi, saya lebih suka unit test menggunakan API publik dan saya pasti tidak pernah membuat metode pribadi publik hanya untuk membuatnya mudah untuk diuji.

Jika Anda benar-benar ingin menguji metode pribadi secara terpisah, di Jawa Anda bisa menggunakan Easymock / Powermock untuk memungkinkan Anda melakukan ini.

Anda harus pragmatis tentang hal itu dan Anda juga harus mengetahui alasan mengapa hal-hal sulit untuk diuji.

' Dengarkan tesnya ' - jika sulit untuk diuji, apakah itu memberi tahu Anda tentang desain Anda? Bisakah Anda refactor ke tempat tes untuk metode ini akan sepele dan mudah ditutup dengan pengujian melalui api publik?

Inilah yang dikatakan Michael Feathers dalam ' Bekerja Secara Efektif dengan Kode Warisan "

"Banyak orang menghabiskan banyak waktu untuk mencari tahu bagaimana mengatasi masalah ini ... jawaban sebenarnya adalah bahwa jika Anda memiliki keinginan untuk menguji metode pribadi, metode tersebut tidak boleh bersifat pribadi; jika membuat metode ini publik mengganggu Anda, kemungkinan besar, itu karena itu adalah bagian dari tanggung jawab yang terpisah; itu harus di kelas lain. " [ Bekerja Efektif dengan Legacy Code (2005) oleh M. Feathers]

kosong
sumber
5
+1 untuk Easymock / Powermock dan tidak pernah membuat metode privat publik
Leonard Brünings
51
+1 Untuk "Dengarkan tes". Jika Anda merasa perlu menguji metode pribadi, kemungkinan bagus bahwa logika dalam metode itu sangat rumit sehingga harus dalam kelas pembantu yang terpisah. Maka Anda dapat menguji kelas pembantu.
Phil
2
'Dengarkan tes' nampaknya menurun. Berikut ini tautan yang di-cache: webcache.googleusercontent.com/...
Jess Telford
1
Saya setuju dengan @Phil (terutama referensi buku), tetapi saya sering menemukan diri saya dalam situasi ayam / telur dengan kode warisan. Saya tahu metode ini termasuk dalam kelas lain, namun saya tidak 100% nyaman refactoring tanpa tes di tempat pertama.
Kevin
Saya setuju dengan dokter. Jika Anda perlu menguji metode pribadi mungkin harus di kelas lain. Anda dapat mencatat bahwa kelas lain / kelas pembantu yang metode privasinya cenderung menjadi publik / dilindungi.
rupweb
67

Seperti yang dikatakan orang lain, agaknya dicurigai sebagai unit yang menguji metode pribadi sama sekali; unit menguji antarmuka publik, bukan detail implementasi pribadi.

Yang mengatakan, teknik yang saya gunakan ketika saya ingin menguji unit sesuatu yang bersifat pribadi di C # adalah menurunkan perlindungan aksesibilitas dari pribadi ke internal, dan kemudian menandai unit pengujian unit sebagai teman perakitan menggunakan InternalsVisibleTo . Unit pengujian unit kemudian akan diizinkan untuk memperlakukan internal sebagai publik, tetapi Anda tidak perlu khawatir tentang penambahan tidak sengaja ke area permukaan publik Anda.

Eric Lippert
sumber
Saya menggunakan teknik ini juga, tetapi sebagai upaya terakhir untuk kode warisan biasanya. Jika Anda perlu menguji kode pribadi, Anda memiliki kelas yang hilang / kelas yang diuji terlalu banyak. Ekstrak kode tersebut ke kelas baru yang dapat Anda uji secara terpisah. Trik seperti ini harus jarang digunakan.
Finglas
Tetapi mengekstraksi kelas dan hanya menguji itu berarti tes Anda kehilangan pengetahuan bahwa kelas asli sebenarnya menggunakan kode yang diekstraksi. Bagaimana Anda menyarankan Anda mengisi celah ini dalam tes Anda? Dan jika jawabannya adalah hanya menguji antarmuka publik kelas asli dengan cara yang menyebabkan logika yang diekstraksi digunakan, lalu mengapa repot-repot mengekstraksi itu di tempat pertama? (Saya tidak bermaksud terdengar konfrontatif di sini, btw - Saya selalu bertanya-tanya bagaimana orang menyeimbangkan pertukaran semacam itu.)
Mal Ross
@mal saya akan memiliki tes kolaborasi. Dengan kata lain tiruan untuk memverifikasi kelas baru dipanggil dengan benar. Lihat jawaban saya di bawah ini.
Finglas
Bisakah seseorang menulis kelas uji yang mewarisi dari kelas dengan logika internal yang ingin Anda uji dan menyediakan metode publik untuk digunakan untuk menguji logika internal?
sholsinger
1
@sholsinger: di C # kelas publik mungkin tidak mewarisi dari kelas internal.
Eric Lippert
45

Banyak jawaban menyarankan hanya menguji antarmuka publik, tetapi IMHO ini tidak realistis - jika metode melakukan sesuatu yang membutuhkan 5 langkah, Anda akan ingin menguji lima langkah secara terpisah, tidak semuanya bersamaan. Ini membutuhkan pengujian kelima metode, yang (selain untuk pengujian) mungkin sebaliknya private.

Cara biasa untuk menguji metode "pribadi" adalah memberikan setiap kelas antarmuka sendiri, dan membuat metode "pribadi" public, tetapi tidak memasukkannya ke dalam antarmuka. Dengan cara ini, mereka masih dapat diuji, tetapi mereka tidak mengasapi antarmuka.

Ya, ini akan menghasilkan file dan class-mengasapi.

Ya, ini memang membuat publicdan privatepenspesifikasi berlebihan.

Ya, ini menyebalkan.

Sayangnya, ini adalah salah satu dari banyak pengorbanan yang kami lakukan untuk membuat kode dapat diuji . Mungkin bahasa yang akan datang (atau bahkan versi C # / Java yang akan datang) akan memiliki fitur untuk membuat kemudahan uji kelas dan modul; tetapi sementara itu, kita harus melewati rintangan ini.


Ada beberapa yang berpendapat bahwa masing-masing langkah itu harus kelasnya sendiri , tetapi saya tidak setuju - jika semuanya berbagi keadaan, tidak ada alasan untuk membuat lima kelas terpisah di mana lima metode akan dilakukan. Lebih buruk lagi, ini menghasilkan file dan kelas mengasapi. Selain itu, ia menginfeksi API publik dari modul Anda - semua kelas tersebut harusnya pasti publicjika Anda ingin mengujinya dari modul lain (baik itu, atau memasukkan kode uji dalam modul yang sama, yang berarti mengirim kode uji dengan produk Anda) .

BlueRaja - Danny Pflughoeft
sumber
2
jawaban yang bagus untuk babak pertama
cmcginty
7
+1: Jawaban terbaik untuk saya. Jika seorang pembuat mobil tidak menguji bagian dari mobilnya untuk alasan yang sama, saya tidak yakin ada yang akan membelinya. Bagian penting dari kode harus diuji bahkan jika kita harus mengubahnya ke publik.
Tae-Sung Shin
1
Jawaban yang sangat bagus. Anda memiliki suara hangat saya. Saya menambahkan jawaban. Mungkin menarik bagi Anda.
davidxxx
29

Tes unit harus menguji kontrak publik, satu-satunya cara bagaimana kelas dapat digunakan di bagian lain dari kode. Metode pribadi adalah detail implementasi, Anda tidak boleh mengujinya, sejauh API publik berfungsi dengan benar, penerapannya tidak masalah dan dapat diubah tanpa perubahan dalam kasus pengujian.

kan
sumber
+1, dan jawaban yang nyata: stackoverflow.com/questions/4603847/…
Raedwald
Afair, Situasi langka yang pernah saya putuskan untuk menggunakan privat dalam kasus uji adalah ketika saya memverifikasi kebenaran objek - jika itu telah menutup semua penangan JNI. Jelas, itu tidak diekspos sebagai API publik, namun, jika itu tidak terjadi, kebocoran memori terjadi. Tapi lucu, kemudian saya menyingkirkannya juga setelah saya memperkenalkan detektor kebocoran memori sebagai bagian dari API publik.
kan
11
Ini adalah logika yang cacat. Titik unit test adalah untuk menangkap kesalahan lebih cepat daripada lebih lambat. Dengan tidak menguji metode pribadi, Anda meninggalkan celah dalam pengujian Anda yang mungkin tidak ditemukan sampai nanti. Dalam kasus di mana metode pribadi kompleks dan tidak mudah dilakukan oleh antarmuka publik, itu harus diuji unit.
cmcginty
6
@Casey: jika metode pribadi tidak dilakukan oleh antarmuka publik mengapa itu ada?
Thilo
2
@DaSilva_Ireland: Akan sulit untuk mempertahankan kasus uji di masa mendatang. Satu-satunya kasus penggunaan kelas nyata adalah melalui metode publik. Yaitu semua kode lain hanya bisa menggunakan kelas melalui API publik. Jadi, jika Anda ingin mengubah implementasi dan gagal metode pengujian kasus swasta, itu tidak berarti bahwa Anda melakukan sesuatu yang salah, apalagi, jika Anda menguji hanya pribadi, tetapi tidak menguji publik secara penuh, itu tidak akan mencakup beberapa bug potensial . Idealnya kasus uji harus mencakup semua kasus yang mungkin dan hanya kasus penggunaan nyata kelas.
kan
22

IMO, Anda harus menulis tes Anda tidak membuat asumsi mendalam tentang bagaimana kelas Anda diimplementasikan di dalam. Anda mungkin ingin memperbaiki nanti menggunakan model internal lain tetapi masih membuat jaminan yang sama dengan yang diberikan implementasi sebelumnya.

Ingatlah itu, saya menyarankan Anda untuk fokus pada pengujian bahwa kontrak Anda masih berlaku tidak peduli apa pun implementasi internal kelas Anda saat ini. Pengujian berbasis properti API publik Anda.

SimY4
sumber
6
Saya setuju bahwa tes unit yang tidak perlu ditulis ulang setelah refactoring kode lebih baik. Dan bukan hanya karena waktu yang Anda habiskan untuk menulis ulang tes unit. Alasan yang jauh lebih penting adalah bahwa saya menganggap tujuan paling penting dari tes unit adalah bahwa kode Anda masih berlaku dengan benar setelah refactoring. Jika Anda harus menulis ulang semua tes unit Anda setelah refactoring, maka Anda kehilangan manfaat dari tes unit.
kasperd
20

Bagaimana kalau menjadikannya paket pribadi? Kemudian kode pengujian Anda dapat melihatnya (dan kelas-kelas lain di paket Anda juga), tetapi masih tersembunyi dari pengguna Anda.

Tapi sungguh, Anda seharusnya tidak menguji metode pribadi. Itu adalah rincian implementasi, dan bukan bagian dari kontrak. Semua yang mereka lakukan harus ditutupi dengan memanggil metode publik (jika mereka memiliki kode di sana yang tidak dilakukan oleh metode publik, maka itu harus pergi). Jika kode pribadi terlalu rumit, kelas mungkin melakukan terlalu banyak hal dan ingin melakukan refactoring.

Membuat metode publik adalah komitmen besar. Setelah Anda melakukannya, orang-orang akan dapat menggunakannya, dan Anda tidak bisa mengubahnya lagi.

Thilo
sumber
+1 jika Anda perlu menguji kemaluan Anda, Anda mungkin kehilangan kelas
jk.
+1 untuk kelas yang hilang. Senang melihat orang lain tidak langsung ikut "tandai internal".
Finglas
Anak laki-laki saya harus pergi jauh untuk menemukan paket jawaban pribadi.
Bill K
17

Pembaruan : Saya telah menambahkan jawaban yang lebih luas dan lebih lengkap untuk pertanyaan ini di banyak tempat lain. Ini dapat ditemukan di blog saya .

Jika saya perlu mempublikasikan sesuatu untuk mengujinya, ini biasanya mengisyaratkan bahwa sistem yang sedang diuji tidak mengikuti Prinsip Single Reponsibility . Karenanya ada kelas yang hilang yang harus diperkenalkan. Setelah mengekstraksi kode ke kelas baru, buat itu publik. Sekarang Anda dapat menguji dengan mudah, dan Anda mengikuti SRP. Kelas Anda yang lain hanya perlu memanggil kelas baru ini melalui komposisi.

Membuat metode publik / menggunakan trik langauge seperti menandai kode yang terlihat untuk menguji majelis harus selalu menjadi pilihan terakhir.

Sebagai contoh:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Perbaiki ini dengan memperkenalkan objek validator.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Sekarang yang harus kita lakukan adalah menguji apakah validator dipanggil dengan benar. Proses validasi aktual (logika pribadi sebelumnya) dapat diuji dalam isolasi murni. Tidak perlu untuk pengujian rumit yang diatur untuk memastikan validasi ini berlalu.

Finglas
sumber
1
Saya pribadi berpikir SRP dapat diambil terlalu jauh, misalnya jika saya menulis layanan akun yang berhubungan dengan memperbarui / membuat / menghapus akun dalam suatu aplikasi, apakah Anda memberi tahu saya bahwa Anda harus membuat kelas terpisah untuk setiap operasi? Dan bagaimana dengan validasi misalnya, apakah benar-benar layak mengekstraksi logika validasi ke kelas lain ketika itu tidak akan pernah digunakan di tempat lain? imo yang hanya membuat kode lebih mudah dibaca, kurang ringkas dan mengemas aplikasi Anda dengan kelas yang berlebihan!
jcvandan
1
Itu semua tergantung skenario. Definisi satu orang tentang "melakukan satu hal" mungkin berbeda dengan yang lain. Dalam hal ini untuk Anda sendiri, jelas kode pribadi yang ingin Anda uji adalah rumit / sulit untuk dibuat. Fakta bahwa Anda ingin mengujinya membuktikan bahwa ia melakukan terlalu banyak. Karena itu ya, memiliki objek baru akan ideal.
Finglas
1
Adapun membuat kode Anda kurang mudah dibaca saya sangat tidak setuju. Memiliki kelas untuk validasi lebih mudah dibaca daripada memiliki validasi yang tertanam di objek lain. Anda masih memiliki jumlah kode yang sama, hanya saja tidak bersarang di satu kelas besar. Saya lebih suka memiliki beberapa kelas yang melakukan satu hal dengan sangat baik, daripada satu kelas besar.
Finglas
4
@dormisher, mengenai layanan akun Anda, pikirkan SRP sebagai "satu alasan untuk berubah." Jika operasi CRUD Anda secara alami akan berubah bersama, maka mereka akan cocok dengan SRP. Biasanya, Anda akan mengubahnya karena skema basis data mungkin berubah, yang secara alami akan memengaruhi penyisipan dan pembaruan (walaupun mungkin tidak selalu menghapus).
Anthony Pegram
@finglas apa pendapat Anda tentang ini: stackoverflow.com/questions/29354729/… . Saya tidak merasa ingin menguji metode privat, jadi mungkin saya tidak boleh memisahkannya menjadi kelas, tetapi sedang digunakan dalam 2 metode publik lainnya, jadi saya akhirnya akan menguji dua kali logika yang sama.
Rodrigo Ruiz
13

Beberapa jawaban bagus. Satu hal yang saya tidak lihat disebutkan adalah bahwa dengan pengembangan uji-didorong (TDD), metode pribadi dibuat selama fase refactoring (lihat Metode Ekstrak untuk contoh pola refactoring), dan karenanya harus sudah memiliki cakupan pengujian yang diperlukan . Jika dilakukan dengan benar (dan tentu saja, Anda akan mendapatkan kumpulan pendapat yang beragam ketika datang ke kebenaran), Anda tidak perlu khawatir tentang harus membuat metode pribadi publik hanya agar Anda dapat mengujinya.

csano
sumber
11

Mengapa tidak membagi algoritma manajemen tumpukan ke dalam kelas utilitas? Kelas utilitas dapat mengelola tumpukan dan menyediakan aksesor publik. Tes unitnya dapat difokuskan pada detail implementasi. Tes mendalam untuk kelas yang rumit secara algoritmik sangat membantu dalam menghilangkan kasus tepi dan memastikan jangkauan.

Kemudian kelas Anda saat ini dapat dengan bersih mendelegasikan ke kelas utilitas tanpa mengekspos detail implementasi. Tesnya akan berhubungan dengan persyaratan pagination seperti yang direkomendasikan orang lain.

Alfred Armstrong
sumber
10

Di java, ada juga opsi untuk menjadikannya paket pribadi (yaitu meninggalkan pengubah visibilitas). Jika pengujian unit Anda berada dalam paket yang sama dengan kelas yang diuji, maka Anda harus dapat melihat metode ini, dan sedikit lebih aman daripada membuat metode tersebut sepenuhnya publik.

Tom Jefferys
sumber
10

Metode pribadi biasanya digunakan sebagai metode "pembantu". Oleh karena itu mereka hanya mengembalikan nilai-nilai dasar dan tidak pernah beroperasi pada instance objek tertentu.

Anda memiliki beberapa opsi jika ingin mengujinya.

  • Gunakan refleksi
  • Berikan metode akses paket

Atau Anda bisa membuat kelas baru dengan metode helper sebagai metode publik jika itu adalah kandidat yang cukup baik untuk kelas baru.

Ada artikel yang sangat bagus di sini tentang ini.

adamjmarkham
sumber
1
Komentar yang adil. Hanya pilihan, mungkin opsi yang buruk.
adamjmarkham
@Finglas Mengapa menguji kode pribadi menggunakan refleksi adalah ide yang buruk?
Anshul
@Anshul metode pribadi adalah detail implementasi dan bukan perilaku. Dengan kata lain, mereka berubah. Nama berubah. Parameter berubah. Isi berubah. Ini berarti tes ini akan gagal sepanjang waktu. Metode publik di sisi lain jauh lebih stabil dalam hal API sehingga lebih tangguh. Memberikan tes Anda pada metode publik memeriksa perilaku dan bukan detail implementasi, Anda harus baik.
Finglas
1
@Finglas Mengapa Anda tidak ingin menguji detail implementasi? Itu kode yang perlu bekerja, dan jika berubah lebih sering, Anda mengharapkannya lebih sering rusak, yang merupakan alasan untuk mengujinya lebih dari API publik. Setelah basis kode Anda stabil, katakanlah suatu saat nanti seseorang datang dan mengubah metode pribadi yang memecah cara kerja kelas Anda secara internal. Tes yang menguji internal kelas Anda akan segera memberi tahu mereka bahwa mereka merusak sesuatu. Ya itu akan membuat mempertahankan tes Anda lebih sulit, tapi itu sesuatu yang Anda harapkan dari pengujian cakupan penuh.
Anshul
1
@Finglas Detail implementasi pengujian tidak salah. Itu pendapat Anda tentang hal itu, tetapi ini adalah topik yang banyak diperdebatkan. Menguji internal memberi Anda beberapa keuntungan, meskipun mereka mungkin tidak diperlukan untuk cakupan penuh. Tes Anda lebih fokus karena Anda menguji metode pribadi secara langsung daripada melalui API publik, yang dapat mengacaukan pengujian Anda. Pengujian negatif lebih mudah karena Anda dapat secara manual membatalkan anggota pribadi dan memastikan bahwa API publik mengembalikan kesalahan yang sesuai sebagai respons terhadap keadaan internal yang tidak konsisten. Ada lebih banyak contoh.
Anshul
9

Jika Anda menggunakan C #, Anda dapat membuat metode internal. Dengan begitu Anda tidak mencemari API publik.

Kemudian tambahkan atribut ke dll

[perakitan: InternalsVisibleTo ("MyTestAssembly")]

Sekarang semua metode terlihat di proyek MyTestAssembly Anda. Mungkin tidak sempurna, tetapi lebih baik daripada membuat metode pribadi publik hanya untuk mengujinya.

Piotr Perak
sumber
7

Gunakan refleksi untuk mengakses variabel pribadi jika perlu.

Tapi sungguh, Anda tidak peduli dengan keadaan internal kelas, Anda hanya ingin menguji bahwa metode publik mengembalikan apa yang Anda harapkan dalam situasi yang dapat Anda antisipasi.

Daniel Alexiuc
sumber
Apa yang akan Anda lakukan dengan metode batal?
IntelliData
@IntelliData Gunakan refleksi untuk mengakses variabel pribadi jika perlu.
Daniel Alexiuc
6

Saya akan mengatakan itu adalah ide yang buruk karena saya tidak yakin apakah Anda mendapatkan manfaat dan berpotensi masalah di telepon. Jika Anda mengubah kontrak panggilan, hanya untuk menguji metode pribadi, Anda tidak menguji kelas tentang bagaimana itu akan digunakan, tetapi membuat skenario buatan yang tidak pernah Anda inginkan terjadi.

Lebih jauh lagi, dengan mendeklarasikan metode sebagai publik, apa yang harus dikatakan bahwa dalam waktu enam bulan (setelah lupa bahwa satu-satunya alasan untuk membuat metode publik adalah untuk pengujian) bahwa Anda (atau jika Anda telah menyerahkan proyek) seseorang yang sama sekali berbeda akan menang. dapat menggunakannya, yang mengarah ke konsekuensi yang mungkin tidak diinginkan dan / atau mimpi buruk pemeliharaan.

beny23
sumber
1
bagaimana jika kelas adalah implementasi kontrak layanan dan hanya digunakan melalui antarmuka itu - cara ini bahkan jika metode pribadi telah dibuat publik, itu tidak akan dipanggil lagi karena tidak terlihat
jcvandan
Saya masih ingin menguji kelas menggunakan API publik (antarmuka), karena jika tidak, jika Anda refactor kelas untuk membagi metode internal, maka Anda harus mengubah tes, yang berarti Anda harus menghabiskan beberapa upaya memastikan bahwa tes baru bekerja sama dengan tes lama.
beny23
Juga, jika satu-satunya cara untuk memastikan bahwa cakupan pengujian mencakup setiap kasus tepi di dalam metode pribadi adalah dengan menyebutnya langsung, itu mungkin menunjukkan bahwa kompleksitas kelas menjamin refactoring untuk menyederhanakannya.
beny23
@dormisher - Jika kelas hanya pernah digunakan melalui antarmuka, maka Anda hanya perlu menguji bahwa metode antarmuka publik berperilaku dengan benar. Jika metode publik melakukan apa yang seharusnya dilakukan, mengapa Anda peduli dengan metode pribadi?
Andrzej Doyle
@ Andrzej - Saya kira saya seharusnya tidak benar-benar tetapi ada situasi di mana saya percaya itu bisa valid, misalnya metode pribadi yang memvalidasi keadaan model, mungkin layak menguji metode seperti ini - tetapi metode seperti ini harus dapat diuji melalui antarmuka publik
jcvandan
6

dalam hal pengujian unit, Anda seharusnya tidak menambahkan lebih banyak metode; Saya percaya Anda akan lebih baik membuat test case tentang first()metode Anda , yang akan dipanggil sebelum setiap tes; maka Anda dapat memanggil beberapa kali - next(), previous()dan last()untuk melihat apakah hasilnya sesuai dengan harapan Anda. Saya kira jika Anda tidak menambahkan lebih banyak metode ke kelas Anda (hanya untuk tujuan pengujian), Anda akan tetap berpegang pada prinsip pengujian "kotak hitam";

Johny
sumber
5

Pertama lihat apakah metode ini harus diekstraksi ke kelas lain dan dipublikasikan. Jika bukan itu masalahnya, buatlah paket itu terlindungi dan di Java beri keterangan dengan @VisibleForTesting .

Noel Yap
sumber
5

Dalam pembaruan Anda, Anda mengatakan bahwa baik untuk hanya menguji menggunakan API publik. Sebenarnya ada dua sekolah di sini.

  1. Pengujian kotak hitam

    Black box school mengatakan bahwa kelas harus dianggap sebagai kotak hitam sehingga tidak ada yang bisa melihat implementasi di dalamnya. Satu-satunya cara untuk menguji ini adalah melalui API publik - sama seperti pengguna kelas akan menggunakannya.

  2. pengujian kotak putih.

    Sekolah white box berpikir secara alami untuk menggunakan pengetahuan tentang implementasi kelas dan kemudian menguji kelas untuk mengetahui bahwa itu berfungsi sebagaimana mestinya.

Saya benar-benar tidak bisa memihak diskusi. Saya hanya berpikir itu akan menarik untuk mengetahui bahwa ada dua cara berbeda untuk menguji kelas (atau perpustakaan atau apa pun).

bengtb
sumber
4

Sebenarnya ada situasi ketika Anda harus melakukan ini (misalnya ketika Anda menerapkan beberapa algoritma yang kompleks). Lakukan saja paket-pribadi dan ini sudah cukup. Tetapi dalam kebanyakan kasus mungkin Anda memiliki kelas yang terlalu rumit yang membutuhkan pemfaktoran logika ke dalam kelas lain.

Stanislav Bashkyrtsev
sumber
4

Metode pribadi yang ingin Anda uji dalam isolasi adalah indikasi bahwa ada "konsep" lain yang terkubur di kelas Anda. Ekstrak "konsep" itu ke kelasnya sendiri dan uji itu sebagai "unit" yang terpisah.

Lihatlah video ini untuk melihat subjek yang sangat menarik.

Jordão
sumber
4

Anda seharusnya tidak pernah membiarkan tes Anda menentukan kode Anda. Saya tidak berbicara tentang TDD atau DD lain yang saya maksud, persis apa yang Anda minta. Apakah aplikasi Anda memerlukan metode itu untuk umum. Jika tidak maka uji mereka. Jika tidak maka jangan dipublikasikan untuk pengujian. Sama dengan variabel dan lainnya. Biarkan kebutuhan aplikasi Anda mendikte kode, dan biarkan tes Anda menguji bahwa kebutuhan terpenuhi. (Sekali lagi saya tidak bermaksud menguji dulu atau tidak maksud saya mengubah struktur kelas untuk memenuhi tujuan pengujian).

Sebaliknya Anda harus "menguji lebih tinggi". Uji metode yang memanggil metode pribadi. Tetapi pengujian Anda harus menguji kebutuhan aplikasi Anda dan bukan "keputusan implementasi" Anda.

Misalnya (kode bod pseudo di sini);

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

Tidak ada alasan untuk menguji "tambah", Anda dapat menguji "buku".

Jangan pernah membiarkan tes Anda membuat keputusan desain kode untuk Anda. Uji bahwa Anda mendapatkan hasil yang diharapkan, bukan bagaimana Anda mendapatkan hasil itu.

kapas
sumber
1
"Jangan pernah membiarkan tes Anda membuat keputusan desain kode untuk Anda" - Saya harus tidak setuju. Sulit untuk diuji adalah bau kode. Jika Anda tidak dapat mengujinya, bagaimana Anda tahu itu tidak rusak?
Alfred Armstrong
Untuk klarifikasi, saya setuju bahwa Anda tidak boleh mengubah desain API eksternal perpustakaan, misalnya. Tetapi Anda mungkin perlu refactor untuk membuatnya lebih dapat diuji.
Alfred Armstrong
Saya telah menemukan bahwa jika Anda perlu refactor untuk menguji, itu bukan bau kode yang harus Anda khawatirkan. Anda memiliki sesuatu yang salah. Tentu saja itu pengalaman pribadi, dan kami berdua mungkin bisa membuat argumen dengan data yang solid dua arah. Saya hanya pernah memiliki "sulit untuk menguji" itu bukan "desain yang buruk" di tempat pertama.
coteyr
Solusi ini bagus untuk metode seperti buku yang mengembalikan nilai - Anda dapat memeriksa kembali jenis dalam menegaskan ; tetapi bagaimana Anda akan memeriksa metode void ?
IntelliData
metode batal melakukan sesuatu atau tidak perlu ada. Periksa sesuatu yang mereka lakukan,
coteyr
2

Saya cenderung setuju bahwa bonus dari unit yang diuji lebih besar daripada masalah meningkatkan visibilitas beberapa anggota. Sedikit perbaikan adalah membuatnya terlindungi dan virtual, lalu menimpanya di kelas uji untuk mengeksposnya.

Atau, jika itu fungsionalitas yang ingin Anda uji secara terpisah, apakah itu tidak menyarankan objek yang hilang dari desain Anda? Mungkin Anda bisa meletakkannya di kelas yang dapat diuji ... maka kelas yang ada hanya mendelegasikan ke instance kelas baru ini.

IanR
sumber
2

Saya biasanya menyimpan kelas tes dalam proyek / perakitan yang sama dengan kelas yang diuji.
Dengan cara ini saya hanya perlu internalvisibilitas untuk membuat fungsi / kelas dapat diuji.

Ini agak menyulitkan proses pembangunan Anda, yang perlu menyaring kelas tes. Saya mencapai ini dengan memberi nama semua kelas pengujian saya TestedClassTestdan menggunakan regex untuk menyaring kelas-kelas itu.

Ini tentu saja hanya berlaku untuk bagian C # / .NET dari pertanyaan Anda

yas4891
sumber
2

Aku akan sering menambahkan metode yang disebut sesuatu seperti validate, verify, check, dll, untuk kelas sehingga bisa disebut untuk menguji keadaan internal suatu objek.

Kadang-kadang metode ini dibungkus dengan blok ifdef (saya menulis sebagian besar dalam C ++) sehingga tidak dikompilasi untuk rilis. Tetapi sering berguna dalam rilis untuk memberikan metode validasi yang berjalan pohon objek program memeriksa hal-hal.

Zan Lynx
sumber
2

Guava memiliki anotasi @VisibleForTesting untuk metode penandaan yang memiliki cakupan yang diperbesar (paket atau publik) yang sebaliknya. Saya menggunakan anotasi @Private untuk hal yang sama.

Meskipun API publik harus diuji, kadang-kadang nyaman dan masuk akal untuk mendapatkan hal-hal yang biasanya tidak bersifat publik.

Kapan:

  • sebuah kelas dibuat secara signifikan lebih mudah dibaca, dalam toto, dengan memecahnya menjadi beberapa kelas,
  • hanya untuk membuatnya lebih dapat diuji,
  • dan memberikan beberapa akses uji ke jeroan akan melakukan trik

sepertinya agama adalah rekayasa yang sebenarnya.

Ed Staub
sumber
2

Saya biasanya meninggalkan metode-metode tersebut protecteddan menempatkan unit test dalam paket yang sama (tetapi dalam proyek atau folder sumber lain), di mana mereka dapat mengakses semua metode yang dilindungi karena class loader akan menempatkan mereka dalam namespace yang sama.

hiergiltdiestfu
sumber
2

Tidak, karena ada cara yang lebih baik untuk menguliti kucing itu.

Beberapa harness uji unit bergantung pada makro dalam definisi kelas yang secara otomatis diperluas untuk membuat kait ketika dibangun dalam mode uji. Gaya sangat C, tapi berhasil.

Idiom OO yang lebih mudah adalah membuat apa pun yang Anda ingin uji "dilindungi" daripada "pribadi". Rangkaian uji mewarisi dari kelas yang diuji, dan kemudian dapat mengakses semua anggota yang dilindungi.

Atau Anda memilih opsi "teman". Secara pribadi ini adalah fitur C ++ saya paling tidak suka karena melanggar aturan enkapsulasi, tetapi kebetulan diperlukan untuk bagaimana C ++ mengimplementasikan beberapa fitur, jadi hei ho.

Lagi pula, jika Anda menguji unit maka kemungkinan besar Anda harus menyuntikkan nilai ke anggota tersebut. Texting kotak putih sangat valid. Dan itu benar-benar akan merusak enkapsulasi Anda.

Graham
sumber
2

Di .Net ada kelas khusus yang disebut PrivateObjectdeigned khusus untuk memungkinkan Anda mengakses metode pribadi kelas.

Lihat lebih lanjut di MSDN atau di sini di Stack Overflow

(Saya ingin tahu bahwa belum ada yang menyebutkannya sejauh ini.)

Ada beberapa situasi yang tidak cukup, dalam hal ini Anda harus menggunakan refleksi.

Tetap saya akan mematuhi rekomendasi umum untuk tidak menguji metode pribadi, namun seperti biasa selalu ada pengecualian.

halo yoel
sumber
1

Seperti yang banyak dicatat oleh komentar orang lain, unit test harus fokus pada API publik. Namun, selain pro dan kontra, Anda dapat memanggil metode pribadi dalam tes unit dengan menggunakan refleksi. Anda tentu saja perlu memastikan keamanan JRE Anda mengizinkannya. Memanggil metode pribadi adalah sesuatu yang digunakan oleh Spring Framework dengan ReflectionUtils itu (lihat makeAccessible(Method)metode).

Berikut adalah contoh kelas kecil dengan metode instance pribadi.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

Dan contoh kelas yang mengeksekusi metode instance pribadi.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Executing B, akan mencetak Doing something private. Jika Anda benar-benar membutuhkannya, refleksi dapat digunakan dalam unit test untuk mengakses metode instance pribadi.

Dan Cruz
sumber
0

Secara pribadi, saya memiliki masalah yang sama ketika menguji metode pribadi dan ini karena beberapa alat pengujian terbatas. Desain Anda tidak baik didorong oleh alat yang terbatas jika tidak menanggapi kebutuhan Anda, ubah alat, bukan desain. Karena Anda meminta C # i tidak dapat mengusulkan alat pengujian yang baik, tetapi untuk Java ada dua alat yang kuat: TestNG dan PowerMock, dan Anda dapat menemukan alat pengujian yang sesuai untuk platform .NET

Xoke
sumber