Saya berdebat dengan kolega programmer mengenai apakah ini merupakan praktik yang baik atau buruk untuk memodifikasi kode yang berfungsi hanya untuk membuatnya dapat diuji (melalui unit test misalnya).
Pendapat saya adalah tidak apa-apa, dalam batas-batas menjaga orientasi objek yang baik dan praktik rekayasa perangkat lunak tentu saja (tidak "membuat semuanya publik", dll).
Pendapat kolega saya adalah bahwa memodifikasi kode (yang berfungsi) hanya untuk tujuan pengujian adalah salah.
Hanya sebuah contoh sederhana, pikirkan potongan kode ini yang digunakan oleh beberapa komponen (ditulis dalam C #):
public void DoSomethingOnAllTypes()
{
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var currentType in types)
{
// do something with this type (e.g: read it's attributes, process, etc).
}
}
Saya telah menyarankan bahwa kode ini dapat dimodifikasi untuk memanggil metode lain yang akan melakukan pekerjaan yang sebenarnya:
public void DoSomething(Assembly asm)
{
// not relying on Assembly.GetExecutingAssembly() anymore...
}
Metode ini menggunakan objek Majelis untuk dikerjakan, memungkinkan untuk melewati Majelis Anda sendiri untuk melakukan pengujian. Kolega saya tidak menganggap ini praktik yang baik.
Apa yang dianggap sebagai praktik yang baik dan umum?
Type
parameter, bukanAssembly
.Jawaban:
Memodifikasi kode agar lebih dapat diuji memiliki manfaat di luar kemampuan uji. Secara umum, kode itu lebih bisa diuji
sumber
Ada (tampaknya) kekuatan lawan yang sedang bermain.
Para pendukung menjaga semua 'detail implementasi' bersifat pribadi biasanya dimotivasi oleh keinginan untuk mempertahankan enkapsulasi. Namun, menjaga segala sesuatu dikunci dan tidak tersedia adalah pendekatan disalahpahami untuk enkapsulasi. Jika menjaga agar segala sesuatu tidak tersedia adalah tujuan akhir, satu-satunya kode enkapsulasi sebenarnya adalah ini:
Apakah kolega Anda mengusulkan untuk menjadikan ini satu-satunya titik akses dalam kode Anda? Haruskah semua kode lain tidak dapat diakses oleh penelepon eksternal?
Hampir tidak. Lalu apa yang membuatnya boleh membuat beberapa metode menjadi publik? Bukankah itu, pada akhirnya, keputusan desain yang subjektif?
Tidak terlalu. Apa yang cenderung membimbing programmer, bahkan pada tingkat yang tidak disadari, adalah, sekali lagi, konsep enkapsulasi. Anda merasa aman mengekspos metode publik ketika ia melindungi invariannya dengan benar .
Saya tidak ingin mengekspos metode pribadi yang tidak melindungi invariants, tetapi sering Anda dapat memodifikasinya sehingga tidak melindungi invariants nya, dan kemudian mengekspos ke publik (tentu saja, dengan TDD, Anda melakukannya dengan sebaliknya).
Membuka API untuk testabilitas adalah hal yang baik , karena apa yang Anda lakukan sebenarnya adalah menerapkan Prinsip Terbuka / Tertutup .
Jika Anda hanya memiliki satu penelepon API, Anda tidak tahu seberapa fleksibel API Anda sebenarnya. Kemungkinannya, itu cukup tidak fleksibel. Pengujian bertindak sebagai klien kedua, memberi Anda umpan balik yang berharga tentang fleksibilitas API Anda .
Jadi, jika tes menyarankan Anda harus membuka API Anda, maka lakukanlah; tetapi pertahankan enkapsulasi, bukan dengan menyembunyikan kompleksitas, tetapi dengan mengekspos kompleksitas dengan cara yang gagal-aman.
sumber
Sepertinya Anda sedang berbicara tentang injeksi ketergantungan . Ini sangat umum, dan IMO, sangat diperlukan untuk testabilitas.
Untuk menjawab pertanyaan yang lebih luas tentang apakah itu ide yang baik untuk memodifikasi kode hanya untuk membuatnya dapat diuji, pikirkan seperti ini: kode memiliki banyak tanggung jawab, termasuk a) untuk dieksekusi, b) untuk dibaca oleh manusia, dan c) untuk diuji. Ketiganya penting, dan jika kode Anda tidak memenuhi ketiga tanggung jawab, maka saya akan mengatakan itu bukan kode yang sangat baik. Jadi modifikasi saja!
sumber
Ini sedikit masalah ayam dan telur.
Salah satu alasan terbesar mengapa bagus untuk memiliki cakupan pengujian kode yang baik adalah memungkinkan Anda melakukan refactor tanpa rasa takut. Tapi Anda berada dalam situasi di mana Anda harus memperbaiki kode untuk mendapatkan cakupan tes yang baik! Dan kolega Anda takut.
Saya melihat sudut pandang kolega Anda. Anda memiliki kode yang (mungkin) berfungsi, dan jika Anda pergi dan refactor - untuk alasan apa pun - ada risiko Anda akan merusaknya.
Tetapi jika ini adalah kode yang diharapkan memiliki pemeliharaan dan modifikasi yang berkelanjutan, Anda akan menjalankan risiko itu setiap kali Anda mengerjakannya. Dan refactoring sekarang dan mendapatkan beberapa cakupan pengujian sekarang akan memungkinkan Anda mengambil risiko itu, dalam kondisi yang terkendali, dan membuat kode menjadi lebih baik untuk modifikasi di masa depan.
Jadi saya akan mengatakan, kecuali basis kode khusus ini cukup statis dan tidak diharapkan untuk melakukan pekerjaan yang signifikan di masa depan, bahwa apa yang ingin Anda lakukan adalah praktik teknis yang baik.
Tentu saja, apakah itu praktik bisnis yang baik adalah seluruh kaleng cacing ..
sumber
Ini mungkin hanya perbedaan dalam penekanan dari jawaban lain, tetapi saya akan mengatakan bahwa kode tersebut tidak boleh di refactored secara ketat untuk meningkatkan testability. Testabilitas sangat penting untuk perawatan, tetapi testabilitas bukanlah tujuan itu sendiri. Karena itu, saya akan menunda refactoring semacam itu sampai Anda dapat memprediksi bahwa kode ini akan memerlukan pemeliharaan untuk melanjutkan bisnis.
Pada titik Anda menentukan bahwa kode ini akan memerlukan beberapa pemeliharaan, itu akan menjadi saat yang tepat untuk refactor untuk diuji. Bergantung pada kasus bisnis Anda, mungkin asumsi yang valid bahwa semua kode pada akhirnya akan memerlukan beberapa pemeliharaan, dalam hal ini perbedaan yang saya gambar dengan jawaban lain di sini ( mis . Jawaban Jason Swett ) menghilang.
Singkatnya: testability saja bukan (IMO) alasan yang cukup untuk refactor basis kode. Testabilitas memiliki peran yang berharga dalam memungkinkan pemeliharaan berdasarkan kode, tetapi merupakan persyaratan bisnis untuk memodifikasi fungsi kode Anda yang seharusnya mendorong refactoring Anda. Jika tidak ada persyaratan bisnis seperti itu, mungkin akan lebih baik untuk mengerjakan sesuatu yang pelanggan Anda pedulikan.
(Kode baru, tentu saja, sedang dipelihara secara aktif, sehingga harus ditulis agar dapat diuji.)
sumber
Saya pikir kolega Anda salah.
Yang lain telah menyebutkan alasan mengapa hal ini sudah baik, tetapi selama Anda diberikan jalan untuk melakukan ini, Anda harus baik-baik saja.
Alasan untuk peringatan ini adalah bahwa membuat perubahan pada kode datang pada biaya kode harus diuji ulang. Tergantung apa yang Anda lakukan, pekerjaan tes ini mungkin sebenarnya merupakan upaya besar dengan sendirinya.
Itu belum tentu tempat Anda untuk membuat keputusan refactoring versus bekerja pada fitur-fitur baru yang akan menguntungkan perusahaan / pelanggan Anda.
sumber
Saya telah menggunakan alat cakupan kode sebagai bagian dari pengujian unit dalam memeriksa apakah semua jalur melalui kode dijalankan. Sebagai coder / tester yang cukup bagus pada saya sendiri, saya biasanya mencakup 80-90% dari jalur kode.
Ketika saya mempelajari jalur yang belum ditemukan dan berusaha untuk beberapa di antaranya, saat itulah saya menemukan bug seperti kasus kesalahan yang "tidak akan pernah terjadi". Jadi ya, memodifikasi kode dan memeriksa cakupan tes membuat kode menjadi lebih baik.
sumber
Masalah Anda di sini adalah bahwa alat pengujian Anda adalah omong kosong. Anda harus bisa mengejek objek itu dan memanggil metode pengujian Anda tanpa mengubahnya - karena walaupun contoh sederhana ini benar-benar sederhana dan mudah dimodifikasi, apa yang terjadi ketika Anda memiliki sesuatu yang jauh lebih rumit.
Banyak orang telah memodifikasi kode mereka untuk memperkenalkan kelas-kelas berbasis IoC, DI dan berbasis antarmuka hanya untuk memungkinkan pengujian unit menggunakan alat uji coba dan unit test yang memerlukan perubahan kode ini. Saya tidak berpikir mereka adalah hal yang sehat, tidak ketika Anda melihat kode yang cukup mudah dan sederhana berubah menjadi kekacauan mimpi buruk dari interaksi yang kompleks yang didorong sepenuhnya oleh kebutuhan untuk membuat setiap metode kelas benar-benar dipisahkan dari yang lain . Dan untuk menambah penghinaan terhadap cedera, kami kemudian memiliki banyak argumen tentang apakah metode pribadi harus diuji unit atau tidak! (tentu saja mereka harus, apa
Masalahnya, tentu saja, adalah sifat alat uji itu.
Ada alat yang lebih baik tersedia sekarang yang bisa membuat perubahan desain ini ke tempat tidur selamanya. Microsoft memiliki Fakes (nee Moles) yang memungkinkan Anda untuk mematikan objek konkret, termasuk yang statis, sehingga Anda tidak perlu lagi mengubah kode agar sesuai dengan alat. Dalam kasus Anda, jika Anda menggunakan Fakes, Anda akan mengganti panggilan GetTypes dengan panggilan Anda sendiri yang mengembalikan data pengujian yang valid dan tidak valid - yang cukup penting, perubahan yang Anda sarankan tidak memberikan sama sekali.
Untuk menjawab: kolega Anda benar, tetapi mungkin karena alasan yang salah. Jangan mengubah kode untuk menguji, mengubah alat uji Anda (atau seluruh strategi pengujian Anda untuk memiliki lebih banyak tes unit gaya-integrasi daripada pengujian berbutir halus seperti itu).
Martin Fowler berdiskusi tentang area ini dalam artikelnya Mocks not Stubs
sumber
Praktik yang baik dan umum adalah dengan menggunakan Unit testing dan debug log . Tes unit memastikan bahwa jika Anda membuat perubahan lebih lanjut dalam program, fungsi lama Anda tidak rusak. Debug log dapat membantu Anda melacak program saat dijalankan.
Terkadang bahkan di luar itu kita perlu memiliki sesuatu hanya untuk tujuan pengujian. Sudah biasa mengubah kode untuk ini. Tetapi perawatan harus dilakukan sedemikian rupa sehingga kode produksi tidak terpengaruh karena hal ini. Dalam C ++ dan C ini dicapai menggunakan MACRO , yang merupakan kompilasi entitas waktu. Maka kode pengujian tidak muncul sama sekali di lingkungan produksi. Tidak tahu apakah ketentuan seperti itu ada di C #.
Juga ketika Anda menambahkan kode pengujian dalam program Anda, itu harus terlihat jelasbahwa bagian kode ini ditambahkan untuk beberapa tujuan pengujian. Atau pengembang yang mencoba memahami kode hanya akan membanjiri bagian kode itu.
sumber
Ada beberapa perbedaan serius antara contoh Anda. Dalam kasus
DoSomethingOnAllTypes()
, ada implikasi yangdo something
berlaku untuk tipe dalam perakitan saat ini. TetapiDoSomething(Assembly asm)
secara jelas menunjukkan bahwa Anda dapat melewati perakitan apa pun untuk itu.Alasan saya menunjukkan ini adalah bahwa banyak langkah ketergantungan-injeksi-untuk-pengujian-saja di luar batas objek asli. Saya tahu Anda berkata " bukan 'membuat semuanya publik' ", tapi itu salah satu kesalahan terbesar dari model itu, diikuti dengan yang satu ini: membuka metode objek hingga penggunaan yang tidak dimaksudkan untuknya.
sumber
Pertanyaan Anda tidak memberi banyak konteks di mana kolega Anda berdebat sehingga ada ruang untuk spekulasi
"praktik buruk" atau tidak tergantung pada bagaimana dan kapan perubahan dilakukan.
Dalam opition saya contoh Anda untuk mengekstrak metode
DoSomething(types)
adalah ok.Tetapi saya telah melihat kode yang tidak ok seperti ini:
Perubahan ini membuat kode lebih sulit dipahami karena Anda meningkatkan jumlah kemungkinan jalur kode.
Yang saya maksud dengan bagaimana dan kapan :
jika Anda memiliki implementasi yang berjalan dan demi "mengimplementasikan kemampuan pengujian" Anda membuat perubahan maka Anda harus menguji kembali aplikasi Anda karena Anda mungkin telah merusak
DoSomething()
metode Anda .Ini
if (!testmode)
lebih sulit untuk dipahami dan diuji daripada metode yang diekstraksi.sumber