Saya telah membaca bahwa menggunakan "baru" dalam konstruktor (untuk objek lain selain yang bernilai sederhana) adalah praktik yang buruk karena membuat pengujian unit tidak mungkin (karena itu kolaborator tersebut perlu dibuat juga dan tidak dapat diejek). Karena saya tidak benar-benar berpengalaman dalam pengujian unit, saya mencoba mengumpulkan beberapa aturan yang akan saya pelajari terlebih dahulu. Juga, apakah ini aturan yang secara umum valid, terlepas dari bahasa yang digunakan?
unit-testing
constructors
Ezoela Vacca
sumber
sumber
new
berarti berbagai hal dalam berbagai bahasa.new
kata kunci. Bahasa apa yang Anda tanyakan?Jawaban:
Selalu ada pengecualian, dan saya mengambil masalah dengan 'selalu' dalam judul, tapi ya, pedoman ini secara umum valid, dan juga berlaku di luar konstruktor juga.
Menggunakan baru dalam konstruktor melanggar D dalam SOLID (prinsip inversi ketergantungan). Itu membuat kode Anda sulit untuk diuji karena pengujian unit adalah tentang isolasi; sulit untuk mengisolasi kelas jika memiliki referensi konkret.
Ini bukan hanya tentang pengujian unit. Bagaimana jika saya ingin mengarahkan repositori ke dua database berbeda sekaligus? Kemampuan untuk lulus dalam konteks saya sendiri memungkinkan saya untuk instantiate dua repositori berbeda yang menunjuk ke lokasi yang berbeda.
Tidak menggunakan yang baru di konstruktor membuat kode Anda lebih fleksibel. Ini juga berlaku untuk bahasa yang dapat menggunakan konstruksi selain
new
untuk inisialisasi objek.Namun, jelas, Anda perlu menggunakan penilaian yang baik. Ada banyak waktu ketika itu
new
boleh digunakan , atau di mana lebih baik tidak melakukannya, tetapi Anda tidak akan memiliki konsekuensi negatif. Di beberapa titik di suatu tempat,new
harus dipanggil. Berhati-hatilah untuk memanggilnew
di dalam kelas yang bergantung pada banyak kelas lainnya.Melakukan sesuatu seperti menginisialisasi koleksi pribadi kosong di konstruktor Anda baik-baik saja, dan menyuntikkannya akan tidak masuk akal.
Semakin banyak referensi yang dimiliki kelas, semakin berhati-hati Anda untuk tidak menelepon
new
dari dalamnya.sumber
head = null
cara apa pun meningkatkan kode? Mengapa meninggalkan koleksi termasuk nol dan membuatnya sesuai permintaan lebih baik daripada melakukannya di konstruktor?Sementara saya lebih suka menggunakan konstruktor untuk hanya menginisialisasi instance baru daripada membuat beberapa objek lain, objek helper ok, dan Anda harus menggunakan penilaian Anda apakah ada sesuatu seperti helper internal atau tidak.
Jika kelas mewakili koleksi, maka itu mungkin memiliki array penolong internal atau daftar atau hashset. Itu akan digunakan
new
untuk membuat pembantu ini dan itu akan dianggap cukup normal. Kelas tidak menawarkan injeksi untuk menggunakan pembantu internal yang berbeda dan tidak punya alasan untuk melakukannya. Dalam hal ini, Anda ingin menguji metode publik objek, yang mungkin pergi ke mengumpulkan, menghapus, dan mengganti elemen dalam koleksi.Dalam beberapa hal, konstruk kelas bahasa pemrograman adalah mekanisme untuk membuat abstraksi tingkat yang lebih tinggi, dan kami membuat abstraksi semacam itu untuk menjembatani kesenjangan antara domain masalah dan primitif bahasa pemrograman. Namun, mekanisme kelas hanyalah alat; itu bervariasi berdasarkan bahasa pemrograman, dan, beberapa abstraksi domain, dalam beberapa bahasa, hanya memerlukan beberapa objek pada tingkat bahasa pemrograman.
Singkatnya, Anda harus menggunakan penilaian apakah abstraksi hanya memerlukan satu atau lebih objek internal / helper, sementara masih dilihat oleh penelepon sebagai abstraksi tunggal, atau, apakah objek lain akan lebih baik terkena penelepon untuk membuat untuk kontrol dependensi, yang akan disarankan, misalnya, ketika pemanggil melihat objek-objek lain dalam menggunakan kelas.
sumber
Tidak semua kolaborator cukup menarik untuk diuji unit secara terpisah, Anda dapat (secara tidak langsung) mengujinya melalui kelas hosting / instantiating. Ini mungkin tidak sejalan dengan gagasan beberapa orang tentang perlunya menguji setiap kelas, setiap metode publik, dll. Terutama ketika melakukan tes sesudahnya. Saat menggunakan TDD, Anda dapat menolak 'kolaborator' ini mengekstraksi kelas yang sudah sepenuhnya diuji dari proses pengujian pertama Anda.
sumber
Not all collaborators are interesting enough to unit-test separately
akhir cerita :-), Kasus ini mungkin dan tidak ada yang berani menyebutkan. @Joppe saya mendorong Anda untuk sedikit menguraikan jawabannya. Misalnya, Anda bisa menambahkan beberapa contoh kelas yang hanya detail implementasi (tidak cocok untuk penggantian) dan bagaimana mereka dapat diekstraksi yang terakhir jika kami anggap perlu.new File()
untuk melakukan sesuatu yang berhubungan dengan file, tidak ada gunanya melarang panggilan itu. Apa yang akan Anda lakukan, tulis tes regresi terhadapFile
modul stdlib ? Tidak mungkin. Di sisi lain, memanggilnew
pada diri diciptakan kelas lebih meragukan.new File()
.Hati-hati mempelajari "aturan" untuk masalah yang belum pernah Anda temui. Jika Anda menemukan beberapa "aturan" atau "praktik terbaik", saya sarankan mencari contoh mainan sederhana di mana aturan ini "seharusnya" digunakan, dan mencoba menyelesaikan masalah itu sendiri , mengabaikan apa yang dikatakan "aturan".
Dalam hal ini, Anda bisa mencoba membuat 2 atau 3 kelas sederhana dan beberapa perilaku yang harus mereka terapkan. Laksanakan kelas dengan cara apa pun yang terasa alami dan tulis unit test untuk setiap perilaku. Buatlah daftar masalah yang Anda temui, misalnya jika Anda memulai dengan hal-hal yang bekerja satu arah, maka harus kembali dan mengubahnya nanti; jika Anda bingung tentang bagaimana hal-hal yang seharusnya cocok bersama; jika Anda merasa terganggu menulis boilerplate; dll.
Kemudian cobalah memecahkan masalah yang sama dengan mengikuti "aturan". Sekali lagi, buat daftar masalah yang Anda temui. Bandingkan daftar tersebut, dan pikirkan tentang situasi mana yang lebih baik ketika mengikuti aturan, dan yang mungkin tidak.
Mengenai pertanyaan Anda yang sebenarnya, saya cenderung memilih pendekatan port dan adapter , di mana kami membuat perbedaan antara "logika inti" dan "layanan" (ini mirip dengan membedakan antara fungsi murni dan prosedur yang efektif).
Inti logika adalah semua tentang menghitung hal-hal "di dalam" aplikasi, berdasarkan pada domain masalah. Ini mungkin berisi kelas seperti
User
,Document
,Order
,Invoice
, dll baik-baik saja untuk memiliki kelas inti panggilannew
untuk kelas inti lainnya, karena mereka "internal" rincian pelaksanaan. Misalnya, membuatOrder
mungkin juga membuatInvoice
danDocument
merinci apa yang dipesan. Tidak perlu mengejek ini selama tes, karena ini adalah hal-hal aktual yang ingin kita uji!Port dan adapter adalah bagaimana logika inti berinteraksi dengan dunia luar. Di sinilah hal-hal seperti
Database
,ConfigFile
,EmailSender
, dll hidup. Ini adalah hal-hal yang membuat pengujian sulit, sehingga disarankan untuk membuat ini di luar logika inti, dan meneruskannya sesuai kebutuhan (baik dengan injeksi ketergantungan, atau sebagai argumen metode, dll.).Dengan cara ini, logika inti (yang merupakan bagian khusus aplikasi, di mana logika bisnis yang penting tinggal, dan tunduk pada churn yang paling banyak) dapat diuji sendiri, tanpa harus peduli dengan database, file, email, dll. Kami hanya dapat mengirimkan beberapa nilai contoh dan memeriksa apakah kami mendapatkan nilai output yang benar.
Port dan adaptor dapat diuji secara terpisah, menggunakan tiruan untuk database, sistem file, dll. Tanpa harus peduli dengan logika bisnis. Kami hanya bisa memberikan beberapa nilai contoh dan memastikan semuanya disimpan / dibaca / dikirim / dll. secara tepat.
sumber
Izinkan saya untuk menjawab pertanyaan, mengumpulkan apa yang saya anggap sebagai poin kunci di sini. Saya akan mengutip beberapa pengguna untuk singkatnya.
Ya, menggunakan
new
konstruktor di dalam sering menyebabkan cacat desain (misalnya kopling ketat) yang membuat desain kami kaku. Sulit untuk diuji ya, tetapi bukan tidak mungkin. Properti yang dimainkan di sini adalah ketahanan (toleransi terhadap perubahan) 1 .Namun demikian, kutipan di atas tidak selalu benar. Dalam beberapa kasus, mungkin ada kelas yang dimaksudkan untuk digabungkan secara ketat . David Arno telah mengomentari pasangan.
Persis. Beberapa kelas (misalnya kelas dalam) bisa jadi hanya detail implementasi dari kelas utama. Ini dimaksudkan untuk diuji bersama dengan kelas utama, dan mereka tidak perlu diganti atau diperpanjang.
Selain itu, jika pemujaan SOLID kita membuat kita mengekstrak kelas - kelas ini , kita bisa melanggar prinsip lain yang baik. Yang disebut Hukum Demeter . Di sisi lain, yang menurut saya sangat penting dari sudut pandang desain.
Jadi kemungkinan jawabannya, seperti biasa, tergantung . Menggunakan
new
konstruktor di dalam bisa menjadi praktik yang buruk. Namun tidak selalu secara sistematis.Jadi, kita perlu mengevaluasi apakah kelas merupakan detail implementasi (sebagian besar kasus tidak) dari kelas utama. Jika ya, biarkan saja. Jika tidak, pertimbangkan teknik seperti Composition Root atau Dependency Injection oleh IoC Containers .
1: tujuan utama SOLID bukan untuk membuat kode kita lebih dapat diuji. Itu untuk membuat kode kita lebih toleran terhadap perubahan. Lebih fleksibel dan konsekuensinya, lebih mudah untuk diuji
Catatan: David Arno, TheWhisperCat, saya harap Anda tidak keberatan saya mengutip Anda.
sumber
Sebagai contoh sederhana, pertimbangkan kodesemu berikut
Karena
new
ini adalah detail implementasi murni dariFoo
, dan keduanyaFoo::Bar
danFoo::Baz
merupakan bagian dariFoo
, ketika pengujian unitFoo
tidak ada gunanya mengejek bagianFoo
. Anda hanya mengejek bagian luarFoo
saat unit-testingFoo
.sumber
Ya, menggunakan 'baru' di kelas root aplikasi Anda adalah bau kode. Ini berarti Anda mengunci kelas agar menggunakan implementasi spesifik, dan tidak akan dapat menggantikan yang lain. Selalu memilih untuk menyuntikkan ketergantungan ke konstruktor. Dengan begitu, Anda tidak hanya akan dapat dengan mudah menyuntikkan dependensi yang diejek selama pengujian, tetapi juga akan membuat aplikasi Anda jauh lebih fleksibel dengan memungkinkan Anda untuk dengan cepat mengganti implementasi yang berbeda jika diperlukan.
EDIT: Untuk downvoters - inilah tautan ke buku pengembangan perangkat lunak yang menandai 'baru' sebagai kemungkinan bau kode: https://books.google.com/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 # v = onepage & q = new% 20keyword% 20code% 20smell & f = false
sumber
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.
Mengapa ini menjadi masalah? Tidak semua ketergantungan dalam pohon ketergantungan harus terbuka untuk penggantiannew
kata kunci bukanlah praktik yang buruk, dan tidak pernah ada. Begitulah cara Anda menggunakan alat yang penting. Anda tidak menggunakan palu godam, misalnya, di mana bola palu bola akan cukup.