Apakah praktik yang buruk untuk memodifikasi kode hanya untuk tujuan pengujian

77

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?

liortal
sumber
5
Metode Anda yang dimodifikasi harus menggunakan Typeparameter, bukan Assembly.
Robert Harvey
Anda benar - Ketik atau Rakitan, intinya adalah bahwa kode tersebut harus memungkinkan penyediaan itu sebagai parameter, meskipun tampaknya fungsi ini hanya digunakan untuk pengujian ...
liortal
5
Mobil Anda memiliki port ODBI untuk "pengujian" - jika segala sesuatunya sempurna itu tidak akan diperlukan. Dugaan saya, Anda lebih dapat diandalkan daripada perangkat lunaknya.
mattnz
24
Apa jaminan yang dia miliki bahwa kode "berfungsi"?
Craige
3
Tentu saja - lakukanlah. Kode yang diuji stabil sepanjang waktu. Tetapi Anda akan memiliki waktu yang jauh lebih mudah untuk menjelaskan bug yang baru diperkenalkan jika mereka datang dengan beberapa perbaikan fitur daripada perbaikan untuk testabilitas
Petter Nordlander

Jawaban:

135

Memodifikasi kode agar lebih dapat diuji memiliki manfaat di luar kemampuan uji. Secara umum, kode itu lebih bisa diuji

  • Lebih mudah dipertahankan,
  • Lebih mudah untuk berpikir tentang,
  • Lebih longgar digabungkan, dan
  • Memiliki desain keseluruhan yang lebih baik, secara arsitektur.
Robert Harvey
sumber
3
Saya tidak menyebutkan hal-hal ini dalam jawaban saya, tetapi saya sepenuhnya setuju. Mengolah kembali kode Anda agar lebih dapat diuji cenderung memiliki beberapa efek samping yang menyenangkan.
Jason Swett
2
+1: Saya umumnya setuju. Tentu saja ada kasus-kasus yang jelas di mana membuat kode dapat diuji merusak tujuan-tujuan bermanfaat lainnya, dan dalam kasus-kasus itu testabilitas terletak pada prioritas terendah. Tetapi secara umum, jika kode Anda tidak fleksibel / dapat dikembangkan untuk diuji, kode itu tidak akan fleksibel / dapat diperluas untuk digunakan atau bertambah tua dengan anggun.
Telastyn
6
Kode yang dapat diuji adalah kode yang lebih baik. Selalu ada risiko yang melekat dalam memodifikasi kode yang tidak memiliki tes di sekitarnya, dan cara termudah dan termurah untuk mengurangi risiko dalam jangka waktu yang sangat singkat adalah meninggalkannya sendiri, yang baik-baik saja ... sampai Anda benar-benar perlu untuk mengubah kode. Yang perlu Anda lakukan adalah menjual manfaat pengujian unit kepada kolega Anda; jika semua orang ada di papan dengan pengujian unit, maka tidak ada argumen bahwa kode perlu diuji. Jika tidak semua orang berada dalam satu unit dengan pengujian unit, tidak ada gunanya.
Pengembara
3
Persis. Saya ingat saya sedang menulis unit test untuk sekitar 10 k baris sumber kode saya sendiri. Saya tahu kode itu bekerja dengan sempurna tetapi tes memaksa saya untuk memikirkan kembali beberapa situasi. Saya harus bertanya pada diri sendiri, "Apa sebenarnya metode ini?" Saya menemukan beberapa bug dalam kode kerja hanya dengan melihatnya dari sudut pandang baru.
Sulthan
2
Saya terus mengklik tombol atas, tetapi saya hanya bisa memberi Anda satu poin. Majikan saya saat ini terlalu picik untuk membiarkan kami benar-benar melaksanakan tes unit, dan saya masih mendapat manfaat dari secara sadar menulis kode saya seolah-olah saya sedang bekerja berdasarkan tes.
AmericanUmlaut
59

Ada (tampaknya) kekuatan lawan yang sedang bermain.

  • Di satu sisi, Anda ingin menegakkan enkapsulasi
  • Di sisi lain, Anda ingin dapat menguji perangkat lunak

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:

static void Main(string[] args)

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.

Mark Seemann
sumber
3
+1 Anda merasa aman mengekspos metode publik ketika ia melindungi invariannya dengan benar.
shambulator
1
Saya sangat menyukai jawaban Anda! :) Berapa kali saya mendengar: "Enkapsulasi adalah proses untuk membuat pribadi beberapa metode dan properti". Ini seperti mengatakan ketika Anda memprogram berorientasi objek, ini karena Anda memprogram dengan objek. . :( Saya tidak terkejut bahwa jawaban yang begitu bersih datang dari THE master injeksi ketergantungan saya pasti akan membaca beberapa kali jawaban Anda untuk membuat saya tersenyum miskin kode warisan lama saya.
Samuel
Saya menambahkan beberapa perubahan pada jawaban Anda. Sebagai tambahan, saya membaca posting Enkapsulasi Properti Anda , dan satu-satunya alasan kuat yang pernah saya dengar untuk menggunakan Properti Otomatis adalah bahwa mengubah variabel publik ke properti publik akan merusak kompatibilitas biner; mengeksposnya sebagai Properti Otomatis dari awal memungkinkan validasi atau fungsionalitas internal lainnya ditambahkan kemudian, tanpa melanggar kode klien.
Robert Harvey
21

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!

Jason Swett
sumber
DI bukanlah pertanyaan utama (saya hanya mencoba memberi contoh), intinya adalah apakah boleh menggunakan metode lain yang semula tidak dimaksudkan untuk dibuat, hanya demi pengujian.
liortal
4
Aku tahu. Saya pikir saya membahas poin utama Anda di paragraf kedua saya, dengan "ya".
Jason Swett
13

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 ..

Carson63000
sumber
Kekhawatiran penting. Biasanya, saya melakukan refactoring yang didukung IDE secara bebas karena kesempatan untuk memecahkan apa pun di sana sangat rendah. Jika refactoring lebih terlibat, saya akan melakukannya hanya ketika saya perlu mengubah kode. Untuk mengganti barang, Anda perlu tes untuk mengurangi risiko, sehingga Anda bahkan mendapatkan nilai bisnis.
Hans-Peter Störr
Intinya adalah: apakah kita ingin menyimpan kode lama karena kita ingin memberikan tanggung jawab kepada objek untuk meminta rakitan saat ini atau karena kita tidak ingin menambahkan beberapa properti publik atau mengubah metode tanda tangan? Saya pikir kode tersebut melanggar SRP dan untuk alasan ini, itu harus di-refactored tidak peduli seberapa takut rekan-rekannya. Tentu jika ini adalah API publik yang digunakan oleh banyak pengguna, Anda harus memikirkan strategi seperti menerapkan fasad atau apa pun yang membantu Anda memberikan kode lama antarmuka yang mencegah terlalu banyak perubahan.
Samuel
7

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.)

Aidan Cully
sumber
2

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.

ozz
sumber
2

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.

Sam Gamoran
sumber
2

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

gbjbaanb
sumber
1

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.

Manoj R
sumber
1

Ada beberapa perbedaan serius antara contoh Anda. Dalam kasus DoSomethingOnAllTypes(), ada implikasi yang do somethingberlaku untuk tipe dalam perakitan saat ini. Tetapi DoSomething(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.

Ross Patterson
sumber
0

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:

public void DoSomethingOnAllTypes()
{
  var types = (!testmode) 
      ? Assembly.GetExecutingAssembly().GetTypes() 
      : getTypesFromTestgenerator();

  foreach (var currentType in types)
  {
     if (!testmode)
     {
        // do something with this type that made the unittest fail and should be skipped.
     }
     // do something with this type (e.g: read it's attributes, process, etc).
  }
}

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.

k3b
sumber