Saya baru saja belajar TDD. Ini pemahaman saya bahwa metode pribadi tidak dapat diuji dan tidak perlu khawatir karena API publik akan memberikan informasi yang cukup untuk memverifikasi integritas suatu objek.
Saya sudah mengerti OOP untuk sementara waktu. Ini pemahaman saya bahwa metode pribadi membuat objek lebih dienkapsulasi, sehingga lebih tahan terhadap perubahan dan kesalahan. Dengan demikian, mereka harus digunakan secara default dan hanya metode-metode yang penting bagi klien yang harus dipublikasikan.
Yah, mungkin bagi saya untuk membuat objek yang hanya memiliki metode pribadi dan berinteraksi dengan objek lain dengan mendengarkan acara mereka. Ini akan sangat dirangkum, tetapi sama sekali tidak dapat diuji.
Juga, itu dianggap praktik buruk untuk menambahkan metode demi pengujian.
Apakah ini berarti TDD bertentangan dengan enkapsulasi? Apa saldo yang tepat? Saya cenderung membuat sebagian besar atau semua metode saya publik sekarang ...
sumber
Jawaban:
Lebih suka pengujian ke antarmuka daripada pengujian pada implementasi.
Ini tergantung pada lingkungan pengembangan Anda, lihat di bawah.
Benar, TDD berfokus pada pengujian antarmuka.
Metode pribadi adalah detail implementasi yang dapat berubah selama siklus faktor-ulang. Seharusnya dimungkinkan untuk faktor ulang tanpa mengubah antarmuka atau perilaku kotak hitam . Bahkan, itu adalah bagian dari manfaat TDD, kemudahan yang Anda gunakan untuk menghasilkan kepercayaan bahwa perubahan internal ke kelas tidak akan memengaruhi pengguna kelas itu.
Bahkan jika kelas tidak memiliki metode umum, itu event yang itu antarmuka publik , dan terhadap yang antarmuka publik bahwa Anda dapat menguji.
Karena peristiwa adalah antarmuka maka itu adalah peristiwa yang Anda perlu hasilkan untuk menguji objek itu.
Lihatlah menggunakan benda tiruan sebagai lem untuk sistem pengujian Anda. Seharusnya dimungkinkan untuk membuat objek tiruan sederhana yang menghasilkan suatu peristiwa dan mengambil perubahan status yang dihasilkan (dimungkinkan oleh objek tiruan penerima lain).
Tentu saja, Anda harus sangat berhati - hati dalam mengekspos keadaan internal.
Benar-benar tidak.
TDD seharusnya tidak mengubah implementasi kelas Anda selain untuk menyederhanakannya (dengan menerapkan YAGNI dari poin sebelumnya).
Praktik terbaik dengan TDD identik dengan praktik terbaik tanpa TDD, Anda baru tahu mengapa lebih cepat, karena Anda menggunakan antarmuka saat Anda mengembangkannya.
Ini lebih baik membuang bayi keluar dengan air mandi.
Anda tidak perlu membuat semua metode terbuka agar Anda dapat mengembangkannya dengan cara TDD. Lihat catatan saya di bawah ini untuk melihat apakah metode pribadi Anda benar-benar tidak dapat diuji.
Pandangan yang lebih rinci pada pengujian metode pribadi
Jika Anda benar-benar harus menguji beberapa perilaku pribadi kelas, tergantung pada bahasa / lingkungannya, Anda mungkin memiliki tiga opsi:
Jelas opsi ke-3 sejauh ini adalah yang terbaik.
1) Letakkan tes di kelas yang ingin Anda uji (tidak ideal)
Menyimpan kasus uji dalam file kelas / sumber yang sama dengan kode produksi yang sedang diuji adalah pilihan paling sederhana. Tetapi tanpa banyak arahan pra-prosesor atau anotasi Anda akan berakhir dengan kode pengujian Anda menggembungkan kode produksi Anda tidak perlu, dan tergantung pada bagaimana Anda telah menyusun kode Anda, Anda mungkin berakhir secara tidak sengaja mengekspos implementasi internal kepada pengguna kode itu.
2) Ekspos metode pribadi yang ingin Anda uji sebagai metode publik (benar-benar bukan ide yang baik)
Seperti yang disarankan ini adalah praktik yang sangat buruk, menghancurkan enkapsulasi dan akan memaparkan implementasi internal kepada pengguna kode.
3) Gunakan lingkungan pengujian yang lebih baik (opsi terbaik, jika tersedia)
Di dunia Eclipse, 3. dapat dicapai dengan menggunakan fragmen . Di dunia C #, kita mungkin menggunakan kelas parsial . Bahasa / lingkungan lain sering memiliki fungsi yang serupa, Anda hanya perlu menemukannya.
Dengan asumsi 1. atau 2. adalah satu-satunya pilihan yang kemungkinan akan menghasilkan perangkat lunak produksi yang penuh dengan kode uji atau antarmuka kelas yang buruk yang mencuci linen kotor mereka di depan umum. * 8 ')
sumber
Tentu saja Anda dapat memiliki metode pribadi, dan tentu saja Anda dapat mengujinya.
Entah ada beberapa cara untuk mendapatkan metode swasta untuk menjalankan, dalam hal ini Anda dapat menguji seperti itu, atau ada tidak ada cara untuk mendapatkan swasta untuk menjalankan, dalam hal: mengapa sih yang Anda mencoba untuk menguji itu, hanya hapus saja!
Dalam contoh Anda:
Mengapa itu tidak dapat diuji? Jika metode ini digunakan sebagai reaksi terhadap suatu peristiwa, mintalah tes untuk memberi makan objek peristiwa yang sesuai.
Ini bukan tentang tidak memiliki metode pribadi, ini tentang tidak melanggar enkapsulasi. Anda dapat memiliki metode pribadi tetapi Anda harus mengujinya melalui API publik. Jika API publik didasarkan pada acara, maka gunakan acara.
Untuk kasus metode pembantu pribadi yang lebih umum, mereka dapat diuji melalui metode publik yang memanggil mereka. Khususnya, karena Anda hanya diperbolehkan menulis kode untuk lulus ujian yang gagal dan tes Anda menguji API publik, semua kode baru yang Anda tulis biasanya akan menjadi publik. Metode pribadi hanya muncul sebagai hasil dari Refactoring Metode Ekstrak , ketika mereka ditarik dari metode publik yang sudah ada. Tetapi dalam kasus itu, tes asli yang menguji metode publik juga masih mencakup metode pribadi, karena metode publik memanggil metode pribadi.
Jadi, biasanya metode pribadi hanya muncul ketika mereka diekstraksi dari metode publik yang sudah diuji dan dengan demikian sudah diuji juga.
sumber
internal
metode atau metode publik diinternal
kelas harus diuji secara langsung cukup sering. Untungnya .net mendukungInternalsVisibleToAttribute
, tetapi tanpa itu, pengujian metode tersebut akan menjadi PITA.Ketika Anda membuat kelas baru dalam kode Anda, Anda melakukannya untuk menjawab beberapa persyaratan. Persyaratan mengatakan apa yang harus dilakukan kode, bukan bagaimana . Ini membuatnya mudah untuk memahami mengapa sebagian besar pengujian terjadi pada tingkat metode publik.
Melalui pengujian, kami memverifikasi bahwa kode melakukan apa yang diharapkan , melemparkan pengecualian yang sesuai saat diharapkan, dll. Kami tidak terlalu peduli bagaimana kode tersebut diterapkan oleh pengembang. Meskipun kami tidak peduli tentang implementasi, yaitu bagaimana kode melakukan apa yang dilakukannya, masuk akal untuk menghindari pengujian metode pribadi.
Sedangkan untuk kelas pengujian yang tidak memiliki metode publik dan berinteraksi dengan dunia luar hanya melalui acara, Anda dapat menguji ini juga dengan mengirimkan, melalui tes, acara dan mendengarkan respons. Sebagai contoh jika suatu kelas harus menyimpan file log setiap kali menerima suatu peristiwa, uji unit akan mengirim peristiwa dan memverifikasi bahwa file log ditulis.
Terakhir namun tidak kalah pentingnya, dalam beberapa kasus sangat sah untuk menguji metode pribadi. Itu sebabnya misalnya dalam. NET, Anda dapat menguji tidak hanya publik, tetapi juga kelas privat, bahkan jika solusinya tidak semudah untuk metode publik.
sumber
Saya tidak setuju dengan pernyataan itu, atau saya akan mengatakan Anda tidak menguji metode pribadi secara langsung . Metode publik dapat memanggil metode pribadi yang berbeda. Mungkin penulis ingin memiliki metode "kecil" dan mengekstraksi beberapa kode menjadi metode pribadi bernama cerdik.
Terlepas dari bagaimana metode publik ditulis, kode pengujian Anda harus mencakup semua jalur. Jika Anda menemukan setelah pengujian Anda bahwa salah satu pernyataan cabang (jika / beralih) dalam satu metode pribadi tidak pernah tercakup dalam pengujian Anda, maka Anda memiliki masalah. Entah Anda melewatkan sebuah kasus dan implementasinya benar ATAU implementasinya salah, dan cabang itu seharusnya tidak pernah ada.
Itulah sebabnya saya sering menggunakan Cobertura dan NCover, untuk memastikan bahwa pengujian metode publik saya juga mencakup metode pribadi. Jangan ragu untuk menulis objek OO yang baik dengan metode pribadi dan jangan biarkan TDD / Pengujian menghalangi Anda dalam hal tersebut.
sumber
Contoh Anda masih dapat diuji dengan sempurna selama Anda menggunakan Ketergantungan Injeksi untuk memberikan contoh yang berinteraksi dengan CUT Anda. Kemudian Anda dapat menggunakan tiruan, menghasilkan acara yang menarik, dan kemudian mengamati apakah CUT mengambil tindakan yang benar pada dependensinya.
Di sisi lain, jika Anda memiliki bahasa dengan dukungan acara yang baik, Anda mungkin mengambil jalur yang sedikit berbeda. Saya tidak suka ketika objek berlangganan ke acara itu sendiri, sebagai gantinya memiliki pabrik yang membuat objek menghubungkan peristiwa dengan metode publik objek. Lebih mudah untuk diuji, dan membuatnya terlihat secara eksternal seperti apa peristiwa yang CUT harus diuji.
sumber
Anda tidak perlu meninggalkan menggunakan metode pribadi. Sangat masuk akal untuk menggunakannya, tetapi dari perspektif pengujian, lebih sulit untuk menguji secara langsung tanpa melanggar enkapsulasi atau menambahkan kode khusus tes ke kelas Anda. Caranya adalah dengan meminimalkan hal-hal yang Anda tahu akan membuat usus Anda menggeliat karena Anda merasa seperti Anda telah mengotori kode Anda.
Ini adalah hal-hal yang saya ingat untuk mencoba dan mencapai keseimbangan yang bisa diterapkan.
Pikirkan secara lateral. Pertahankan kelas Anda kecil dan metode Anda lebih kecil, dan gunakan banyak komposisi. Kedengarannya seperti lebih banyak pekerjaan, tetapi pada akhirnya Anda akan berakhir dengan lebih banyak item yang dapat diuji secara individual, tes Anda akan lebih sederhana, Anda akan memiliki lebih banyak pilihan untuk menggunakan tiruan sederhana di tempat benda nyata, besar dan kompleks, mudah-mudahan well- kode faktor dan longgar digabungkan, dan yang lebih penting Anda akan memberi diri Anda lebih banyak pilihan. Menjaga hal-hal kecil cenderung menghemat waktu Anda pada akhirnya, karena Anda mengurangi jumlah hal yang perlu Anda periksa secara individual di setiap kelas, dan Anda cenderung mengurangi spaghetti kode yang kadang-kadang dapat terjadi ketika kelas menjadi besar dan memiliki banyak perilaku kode interdependen secara internal.
sumber
Bagaimana reaksi objek ini terhadap peristiwa itu? Agaknya, itu harus memanggil metode pada objek lain. Anda dapat mengujinya dengan memeriksa apakah metode-metode itu dipanggil. Apakah itu memanggil objek tiruan dan kemudian Anda dapat dengan mudah menyatakan bahwa ia melakukan apa yang Anda harapkan.
Masalahnya adalah kita hanya ingin menguji interaksi objek dengan objek lain. Kami tidak peduli apa yang terjadi di dalam suatu objek. Jadi tidak, Anda seharusnya tidak memiliki metode publik lebih dari sebelumnya.
sumber
Saya telah berjuang dengan masalah yang sama juga. Sungguh, cara untuk menyiasatinya adalah ini: Bagaimana Anda mengharapkan sisa program Anda berinteraksi dengan kelas itu? Tes kelas Anda sesuai. Ini akan memaksa Anda untuk mendesain kelas Anda berdasarkan bagaimana program lainnya berinteraksi dengannya dan akan, pada kenyataannya, mendorong enkapsulasi dan desain yang baik dari kelas Anda.
sumber
Alih-alih penggunaan pribadi pengubah standar. Kemudian Anda dapat menguji metode-metode tersebut secara individual, tidak hanya digabungkan dengan metode publik. Ini mengharuskan tes Anda memiliki struktur paket yang sama dengan kode utama Anda.
sumber
internal
dalam .net.Beberapa metode pribadi biasanya tidak menjadi masalah. Anda cukup mengujinya melalui API publik seolah-olah kode itu dimasukkan ke dalam metode publik Anda. Kelebihan metode pribadi mungkin merupakan tanda kohesi yang buruk. Kelas Anda harus memiliki satu tanggung jawab yang kohesif, dan seringkali orang membuat metode pribadi untuk memberikan kesan keterpaduan di mana tidak ada yang benar-benar ada.
Misalnya, Anda mungkin memiliki pengendali aktivitas yang membuat banyak panggilan basis data sebagai respons terhadap peristiwa tersebut. Karena jelas merupakan praktik yang buruk untuk membuat event handler untuk membuat panggilan basis data, godaannya adalah membuat semua panggilan yang terkait dengan basis data metode pribadi, ketika mereka harus benar-benar ditarik keluar ke kelas yang terpisah.
sumber
TDD tidak berselisih dengan enkapsulasi. Ambil contoh paling sederhana dari metode atau properti pengambil, tergantung pada bahasa pilihan Anda. Katakanlah saya memiliki objek Pelanggan dan saya ingin memiliki bidang ID. Tes pertama yang akan saya tulis adalah tes yang mengatakan sesuatu seperti "customer_id_initializes_to_zero". Saya mendefinisikan pengambil untuk melempar pengecualian yang tidak diimplementasikan dan menonton tes gagal. Kemudian, hal paling sederhana yang bisa saya lakukan untuk membuat tes lulus adalah mendapatkan pengambil kembali nol.
Dari sana, saya pergi ke tes lain, mungkin yang melibatkan ID pelanggan menjadi bidang fungsional aktual. Pada titik tertentu, saya mungkin harus membuat bidang pribadi yang digunakan kelas pelanggan untuk melacak apa yang harus dikembalikan oleh pengambil. Bagaimana tepatnya cara saya melacak ini? Apakah ini backing int sederhana? Apakah saya melacak string dan kemudian mengubahnya menjadi int? Apakah saya melacak 20 int dan rata-rata? Dunia luar tidak peduli - dan tes TDD Anda tidak peduli. Itu adalah detail yang dirangkum .
Saya pikir ini tidak selalu segera jelas ketika memulai TDD - Anda tidak menguji metode apa yang dilakukan secara internal - Anda menguji kurang perhatian granular kelas. Jadi, Anda tidak mencari untuk menguji metode yang
DoSomethingToFoo()
instantiates Bar, memanggil metode di atasnya, menambahkan dua ke salah satu propertinya, dll. Anda sedang menguji bahwa setelah Anda mengubah keadaan objek Anda, beberapa accessor negara telah berubah (atau tidak). Itulah pola umum dari tes Anda: "ketika saya melakukan X ke kelas saya yang diuji, saya kemudian bisa mengamati Y". Bagaimana hal itu sampai ke Y bukan urusan tes, dan ini adalah apa yang dienkapsulasi dan inilah mengapa TDD tidak bertentangan dengan enkapsulasi.sumber
Hindari pemakaian? Tidak.
Hindari mulai dengan ? Iya.
Saya perhatikan Anda tidak bertanya apakah boleh memiliki kelas abstrak dengan TDD; jika Anda memahami bagaimana kelas abstrak muncul selama TDD, prinsip yang sama juga berlaku untuk metode pribadi.
Anda tidak dapat langsung menguji metode dalam kelas abstrak sama seperti Anda tidak bisa langsung menguji metode pribadi, tapi itu sebabnya Anda tidak memulai dengan kelas abstrak dan metode pribadi; Anda mulai dengan kelas-kelas konkret dan API publik, dan kemudian Anda refactor fungsi umum saat Anda pergi.
sumber