Saat ini ada perdebatan di tim kami mengenai apakah memodifikasi desain kode untuk memungkinkan pengujian unit adalah bau kode, atau sejauh mana hal itu dapat dilakukan tanpa menjadi bau kode. Ini terjadi karena kami baru saja mulai menerapkan praktik yang ada di hampir setiap perusahaan pengembang perangkat lunak lainnya.
Secara khusus, kami akan memiliki layanan API Web yang akan sangat tipis. Tanggung jawab utamanya adalah menyusun permintaan / tanggapan web dan memanggil API yang mendasari yang berisi logika bisnis.
Salah satu contoh adalah bahwa kami berencana membuat pabrik yang akan mengembalikan jenis metode otentikasi. Kami tidak memerlukannya untuk mewarisi antarmuka karena kami tidak mengantisipasinya selain jenis konkretnya. Namun, untuk menguji unit layanan Web API kita perlu mengejek pabrik ini.
Ini pada dasarnya berarti kita merancang kelas pengontrol API Web untuk menerima DI (melalui konstruktor atau setternya), yang berarti kita sedang merancang bagian dari pengontrol hanya untuk memungkinkan DI dan mengimplementasikan antarmuka yang tidak kita perlukan, atau kita menggunakan kerangka kerja pihak ketiga seperti Ninject untuk menghindari keharusan mendesain controller dengan cara ini, tetapi kita masih harus membuat antarmuka.
Beberapa anggota tim tampaknya enggan mendesain kode hanya untuk kepentingan pengujian. Tampaknya bagi saya bahwa harus ada kompromi jika Anda berharap untuk unit test, tapi saya tidak yakin bagaimana menghilangkan kekhawatiran mereka.
Untuk lebih jelasnya, ini adalah proyek baru, jadi ini bukan tentang memodifikasi kode untuk mengaktifkan pengujian unit; ini tentang mendesain kode yang akan kita tulis agar unit dapat diuji.
Jawaban:
Keengganan untuk memodifikasi kode demi pengujian menunjukkan bahwa pengembang belum memahami peran pengujian, dan implikasinya, peran mereka sendiri dalam organisasi.
Bisnis perangkat lunak berputar di sekitar memberikan basis kode yang menciptakan nilai bisnis. Kami telah menemukan, melalui pengalaman yang panjang dan pahit, bahwa kami tidak dapat membuat basis kode dengan ukuran nontrivial tanpa pengujian. Oleh karena itu, suite tes merupakan bagian integral dari bisnis.
Banyak coders yang tidak setuju dengan prinsip ini tetapi secara tidak sadar tidak pernah menerimanya. Sangat mudah untuk memahami mengapa ini terjadi; kesadaran bahwa kemampuan mental kita sendiri tidak terbatas, dan pada kenyataannya, secara mengejutkan terbatas ketika dihadapkan dengan kompleksitas yang sangat besar dari basis kode modern, tidak disukai dan dengan mudah ditekan atau dirasionalisasi. Fakta bahwa kode pengujian tidak dikirimkan kepada pelanggan membuatnya mudah untuk percaya bahwa itu adalah warga negara kelas dua dan tidak penting dibandingkan dengan kode bisnis "esensial". Dan gagasan menambahkan kode pengujian ke kode bisnis tampaknya menyinggung banyak orang.
Masalah dengan membenarkan praktik ini berkaitan dengan fakta bahwa seluruh gambaran tentang bagaimana nilai dibuat dalam bisnis perangkat lunak sering hanya dipahami oleh atasan dalam hierarki perusahaan, tetapi orang-orang ini tidak memiliki pemahaman teknis terperinci tentang alur kerja pengkodean yang diperlukan untuk memahami mengapa pengujian tidak bisa dihilangkan. Karena itu mereka terlalu sering ditenangkan oleh para praktisi yang meyakinkan mereka bahwa pengujian mungkin merupakan ide yang baik secara umum, tetapi "kami adalah programmer elit yang tidak membutuhkan kruk seperti itu", atau bahwa "kami tidak punya waktu untuk itu sekarang", dll. Fakta bahwa kesuksesan bisnis adalah permainan angka dan menghindari hutang teknis, memastikan kualitas dll. menunjukkan nilainya hanya dalam jangka panjang berarti bahwa mereka seringkali cukup tulus dalam kepercayaan itu.
Singkatnya: membuat kode dapat diuji adalah bagian penting dari proses pengembangan, tidak berbeda dengan di bidang lain (banyak microchip dirancang dengan proporsi elemen yang substansial hanya untuk tujuan pengujian), tetapi sangat mudah untuk mengabaikan alasan yang sangat bagus untuk bahwa. Jangan jatuh ke dalam perangkap itu.
sumber
Ini tidak sesederhana yang Anda bayangkan. Mari kita jabarkan.
TAPI!
Setiap perubahan pada kode Anda dapat menimbulkan bug. Jadi mengubah kode tanpa alasan bisnis yang baik bukanlah ide yang baik.
Webapi 'sangat tipis' Anda sepertinya bukan kasus terbesar untuk pengujian unit.
Mengubah kode dan tes pada saat yang sama adalah hal yang buruk.
Saya akan menyarankan pendekatan berikut:
Tulis tes integrasi . Ini seharusnya tidak memerlukan perubahan kode apa pun. Ini akan memberi Anda kasus uji dasar dan memungkinkan Anda untuk memeriksa bahwa perubahan kode lebih lanjut yang Anda buat tidak menimbulkan bug.
Pastikan kode baru dapat diuji dan memiliki tes unit dan integrasi.
Pastikan rantai CI Anda menjalankan tes setelah pembuatan dan penerapan.
Ketika Anda sudah menyiapkan hal-hal itu, baru mulai berpikir tentang refactoring proyek warisan untuk diuji.
Semoga semua orang mendapat pelajaran dari proses tersebut dan memiliki ide bagus tentang di mana pengujian sangat dibutuhkan, bagaimana Anda ingin menyusunnya dan nilai yang diberikannya pada bisnis.
EDIT : Sejak saya menulis jawaban ini, OP telah mengklarifikasi pertanyaan untuk menunjukkan bahwa mereka berbicara tentang kode baru, bukan modifikasi pada kode yang ada. Saya mungkin secara naif berpikir, "Apakah pengujian unit bagus?" Argumen diselesaikan beberapa tahun lalu.
Sulit membayangkan perubahan kode apa yang diperlukan oleh unit test tetapi bukan praktik umum yang baik yang Anda inginkan dalam hal apa pun. Mungkin akan bijaksana untuk memeriksa keberatan yang sebenarnya, mungkin itu adalah gaya pengujian unit yang sedang keberatan.
sumber
Merancang kode agar bisa diuji secara inheren bukanlah bau kode; sebaliknya, itu adalah tanda desain yang bagus. Ada beberapa pola desain yang terkenal dan banyak digunakan berdasarkan ini (misalnya, Model-View-Presenter) yang menawarkan pengujian mudah (lebih mudah) sebagai keuntungan besar.
Jadi, jika Anda perlu menulis antarmuka untuk kelas beton Anda agar lebih mudah mengujinya, itu adalah hal yang baik. Jika Anda sudah memiliki kelas konkret, sebagian besar IDE dapat mengekstrak antarmuka darinya, membuat upaya yang diperlukan minimal. Ini sedikit lebih banyak pekerjaan untuk menjaga keduanya dalam sinkronisasi, tetapi antarmuka tidak boleh banyak berubah, dan manfaat dari pengujian mungkin lebih besar daripada upaya ekstra.
Di sisi lain, sebagai @MatthieuM. disebutkan dalam komentar, jika Anda menambahkan titik masuk spesifik ke dalam kode Anda yang seharusnya tidak pernah digunakan dalam produksi, semata-mata demi pengujian, itu mungkin menjadi masalah.
sumber
_ForTest
) dan periksa basis kode untuk panggilan dari kode non-tes.Ini adalah IMHO sangat sederhana untuk memahami bahwa untuk membuat unit test, kode yang akan diuji harus memiliki setidaknya beberapa properti tertentu. Misalnya, jika kode tidak terdiri dari unit-unit individual yang dapat diuji secara terpisah, kata "unit testing" bahkan tidak masuk akal. Jika kode tidak memiliki properti ini, itu harus diubah terlebih dahulu, itu cukup jelas.
Mengatakan bahwa, secara teori, seseorang dapat mencoba untuk menulis beberapa unit kode yang dapat diuji terlebih dahulu, menerapkan semua prinsip SOLID, dan kemudian mencoba untuk menulis tes untuk itu setelahnya, tanpa lebih lanjut memodifikasi kode asli. Sayangnya, penulisan kode yang benar-benar unit yang dapat diuji tidak selalu sederhana, sehingga sangat mungkin akan ada beberapa perubahan yang diperlukan yang hanya akan terdeteksi ketika mencoba membuat tes. Ini berlaku untuk kode bahkan ketika ditulis dengan ide pengujian unit dalam pikiran, dan jelas lebih benar untuk kode yang ditulis di mana "unit testability" tidak ada dalam agenda di awal.
Ada pendekatan terkenal yang mencoba memecahkan masalah dengan menulis unit test terlebih dahulu - ini disebut Test Driven Development (TDD), dan tentunya dapat membantu membuat kode lebih unit dapat diuji langsung dari awal.
Tentu saja, keengganan untuk mengubah kode setelah itu agar dapat diuji muncul sering dalam situasi di mana kode diuji secara manual terlebih dahulu dan / atau berfungsi dengan baik dalam prodcution, jadi mengubah itu sebenarnya dapat memperkenalkan bug baru, itu benar. Pendekatan terbaik untuk memitigasi hal ini adalah dengan membuat suite uji regresi terlebih dahulu (yang sering dapat diimplementasikan dengan perubahan yang sangat minimal pada basis kode), serta langkah-langkah lain yang menyertainya seperti ulasan kode, atau sesi tes manual baru. Itu harus Anda berikan kepercayaan yang cukup untuk memastikan mendesain ulang beberapa internal tidak merusak sesuatu yang penting.
sumber
Saya menerima masalah dengan pernyataan (tidak berdasar) yang Anda buat:
Itu belum tentu benar. Ada banyak cara untuk menulis tes, dan ada yang cara untuk menulis tes unit yang tidak melibatkan mengolok-olok. Lebih penting lagi, ada jenis tes lain, seperti tes fungsional atau integrasi. Sering kali mungkin untuk menemukan "test seam" di "antarmuka" yang bukan bahasa pemrograman OOP
interface
.Beberapa pertanyaan untuk membantu Anda menemukan jahitan tes alternatif, yang mungkin lebih alami:
Pernyataan tidak berdasar lain yang Anda buat adalah tentang DI:
Ketergantungan injeksi tidak harus berarti membuat yang baru
interface
. Misalnya, dalam penyebab token otentikasi: dapat Anda cukup membuat nyata otentikasi tanda pemrograman? Kemudian tes dapat membuat token tersebut dan menyuntikkannya. Apakah proses untuk memvalidasi token bergantung pada semacam rahasia kriptografi? Saya harap Anda tidak memiliki hardcoded rahasia - saya berharap Anda dapat membacanya dari penyimpanan entah bagaimana, dan dalam hal ini Anda dapat menggunakan rahasia (terkenal) yang berbeda dalam kasus uji Anda.Ini bukan untuk mengatakan bahwa Anda tidak boleh membuat yang baru
interface
. Tetapi jangan terpaku pada hanya ada satu cara untuk menulis tes, atau satu cara untuk memalsukan perilaku. Jika Anda berpikir di luar kotak, Anda biasanya dapat menemukan solusi yang akan membutuhkan sedikit kontorsi kode Anda, namun tetap memberi Anda efek yang Anda inginkan.sumber
Anda beruntung karena ini adalah proyek baru. Saya telah menemukan bahwa Test Driven Design bekerja sangat baik untuk menulis kode yang baik (itulah sebabnya kami melakukannya di tempat pertama).
Dengan mencari tahu di muka bagaimana memanggil sepotong kode tertentu dengan data input realistis, dan kemudian mendapatkan data output realistis yang dapat Anda periksa sebagaimana dimaksud, Anda melakukan desain API sangat awal dalam proses dan memiliki peluang bagus untuk mendapatkan desain yang berguna karena Anda tidak terhalang oleh kode yang ada yang harus ditulis ulang untuk mengakomodasi. Juga lebih mudah dipahami oleh rekan-rekan Anda sehingga Anda dapat melakukan diskusi yang baik lagi di awal proses.
Perhatikan bahwa "berguna" dalam kalimat di atas tidak hanya berarti bahwa metode yang dihasilkan mudah dipanggil, tetapi juga bahwa Anda cenderung mendapatkan antarmuka yang bersih yang mudah dipasang dalam tes integrasi, dan untuk menulis maket.
Pikirkan itu. Apalagi dengan peer review. Dalam pengalaman saya, investasi waktu dan usaha akan sangat cepat dikembalikan.
sumber
UserCanChangeTheirPassword
, maka dalam tes Anda memanggil fungsi (belum ada) untuk mengubah kata sandi dan kemudian Anda menyatakan bahwa kata sandi memang diubah. Kemudian Anda menulis fungsi, sampai Anda dapat menjalankan tes dan tidak membuang pengecualian atau memiliki pernyataan yang salah. Jika pada saat itu Anda punya alasan untuk menambahkan kode, maka alasan itu masuk ke tes lain, misalnyaUserCantChangePasswordToEmptyString
.CalculateFactorial
yang hanya mengembalikan 120 dan tes berlalu. Itu adalah minimum. Ini juga jelas bukan apa yang dimaksudkan, tetapi itu hanya berarti Anda perlu tes lain untuk mengekspresikan apa yang dimaksudkan.Jika Anda perlu memodifikasi kode, itu adalah bau kode.
Dari pengalaman pribadi, jika kode saya sulit untuk menulis tes, itu kode yang buruk. Ini bukan kode yang buruk karena tidak berjalan atau berfungsi seperti yang dirancang, ini buruk karena saya tidak dapat dengan cepat memahami mengapa ia bekerja. Jika saya menemukan bug, saya tahu itu akan menjadi pekerjaan yang menyakitkan untuk memperbaikinya. Kode ini juga sulit / tidak mungkin untuk digunakan kembali.
Kode Good (Clean) memecah tugas menjadi bagian-bagian yang lebih kecil yang mudah dipahami dalam sekejap (atau setidaknya terlihat bagus). Menguji bagian yang lebih kecil ini mudah. Saya juga dapat menulis tes yang hanya menguji sepotong basis kode dengan kemudahan yang sama jika saya cukup yakin tentang subbagian (menggunakan kembali juga membantu di sini karena telah diuji).
Buat kode mudah untuk diuji, mudah untuk refactor, dan mudah untuk digunakan kembali dari awal dan Anda tidak akan bunuh diri kapan pun Anda perlu melakukan perubahan.
Saya sedang mengetik ini saat benar-benar membangun kembali sebuah proyek yang seharusnya menjadi prototipe sekali pakai menjadi kode yang lebih bersih. Adalah jauh lebih baik untuk memperbaikinya sejak awal dan memperbaiki kode buruk sesegera mungkin daripada menatap layar selama berjam-jam karena takut menyentuh apa pun karena takut merusak sesuatu yang sebagian berfungsi.
sumber
Saya berpendapat bahwa menulis kode yang tidak dapat diuji unit adalah bau kode. Secara umum, jika kode Anda tidak dapat diuji unit, maka itu bukan modular, yang membuatnya sulit untuk dipahami, dipertahankan, atau ditingkatkan. Mungkin jika kode tersebut adalah kode lem yang benar-benar hanya masuk akal dalam hal pengujian integrasi, Anda dapat mengganti pengujian integrasi untuk pengujian unit, tetapi meskipun integrasi gagal, Anda harus mengisolasi masalahnya dan pengujian unit adalah cara yang bagus untuk lakukan.
Kamu bilang
Saya tidak benar-benar mengikuti ini. Alasan memiliki pabrik yang menciptakan sesuatu adalah untuk memungkinkan Anda mengubah pabrik atau mengubah apa yang dibuat pabrik dengan mudah, sehingga bagian lain dari kode tidak perlu diubah. Jika metode otentikasi Anda tidak akan pernah berubah, maka pabrik itu adalah kode yang tidak berguna. Namun, jika Anda ingin memiliki metode otentikasi yang berbeda dalam pengujian daripada dalam produksi, memiliki pabrik yang mengembalikan metode otentikasi yang berbeda dalam pengujian daripada dalam produksi adalah solusi yang bagus.
Anda tidak perlu DI atau mengejek untuk ini. Anda hanya perlu pabrik Anda untuk mendukung berbagai jenis otentikasi dan untuk itu dapat dikonfigurasi, entah bagaimana, dari file konfigurasi atau variabel lingkungan.
sumber
Dalam setiap disiplin teknik yang dapat saya pikirkan, hanya ada satu cara untuk mencapai tingkat kualitas yang layak atau lebih tinggi:
Untuk memperhitungkan inspeksi / pengujian dalam desain.
Ini berlaku dalam konstruksi, desain chip, pengembangan perangkat lunak, dan manufaktur. Sekarang, ini tidak berarti bahwa pengujian adalah pilar yang harus dibangun setiap desain, bukan sama sekali. Tetapi dengan setiap keputusan desain, perancang harus jelas tentang dampak pada biaya pengujian dan membuat keputusan sadar tentang trade off.
Dalam beberapa kasus, pengujian manual atau otomatis (misalnya Selenium) akan lebih nyaman daripada Tes Unit, sementara juga memberikan cakupan tes yang dapat diterima sendiri. Dalam kasus yang jarang terjadi, melemparkan sesuatu ke luar sana yang hampir seluruhnya belum diuji juga dapat diterima. Tetapi ini harus menjadi kasus per kasus keputusan sadar. Memanggil desain yang diperhitungkan untuk menguji "bau kode" menunjukkan kurangnya pengalaman yang serius.
sumber
Saya telah menemukan bahwa pengujian unit (dan jenis pengujian otomatis lainnya) memiliki kecenderungan untuk mengurangi bau kode, dan saya tidak bisa memikirkan satu contoh pun di mana mereka memperkenalkan bau kode. Tes unit biasanya memaksa Anda untuk menulis kode yang lebih baik. Jika Anda tidak dapat menggunakan metode dengan mudah dalam pengujian, mengapa lebih mudah dalam kode Anda?
Tes unit yang ditulis dengan baik menunjukkan kepada Anda bagaimana kode ini dimaksudkan untuk digunakan. Mereka adalah bentuk dokumentasi yang dapat dieksekusi. Saya telah melihat tes unit yang ditulis dengan sangat mengerikan dan terlalu panjang yang tidak dapat dipahami. Jangan tulis itu! Jika Anda perlu menulis tes panjang untuk mengatur kelas Anda, kelas Anda perlu refactoring.
Tes unit akan menyoroti di mana beberapa kode Anda berbau. Saya akan menyarankan membaca Michael C. Feathers ' Bekerja Efektif dengan Legacy Code . Meskipun proyek Anda baru, jika belum memiliki (atau banyak) pengujian unit, Anda mungkin memerlukan beberapa teknik yang tidak jelas untuk membuat kode Anda diuji dengan baik.
sumber
Pendeknya:
Kode yang dapat diuji adalah (biasanya) kode yang dapat dipelihara - atau lebih tepatnya, kode yang sulit untuk diuji biasanya sulit untuk dipelihara. Merancang kode yang tidak dapat diuji mirip dengan merancang mesin yang tidak dapat diperbaiki - kasihan orang miskin yang akan ditugaskan untuk memperbaikinya pada akhirnya (mungkin Anda).
Anda tahu bahwa Anda akan memerlukan lima jenis tipe metode otentikasi dalam tiga tahun, setelah Anda mengatakannya, bukan? Persyaratan berubah, dan walaupun Anda harus menghindari rekayasa yang berlebihan pada desain Anda, memiliki desain yang dapat diuji berarti bahwa desain Anda memiliki (hanya) jahitan yang cukup untuk diubah tanpa (terlalu banyak) rasa sakit - dan bahwa tes modul akan memberi Anda cara otomatis untuk melihat bahwa perubahan Anda tidak merusak apa pun.
sumber
Mendesain injeksi ketergantungan bukanlah bau kode - ini praktik terbaik. Menggunakan DI tidak hanya untuk testabilitas. Membangun komponen-komponen Anda di sekitar DI bantu modularitas dan usabilitas, lebih mudah memungkinkan komponen utama untuk ditukar (seperti lapisan antarmuka basis data). Sementara itu menambah tingkat kerumitan, dilakukan dengan benar memungkinkan untuk pemisahan lapisan yang lebih baik dan isolasi fungsionalitas yang membuat kompleksitas lebih mudah untuk dikelola dan dinavigasi. Ini membuatnya lebih mudah untuk memvalidasi perilaku masing-masing komponen dengan benar, mengurangi bug, dan juga dapat mempermudah melacak bug.
sumber
Mari kita lihat perbedaan antara yang diuji:
dan pengontrol yang tidak dapat diuji:
Opsi sebelumnya secara harfiah memiliki 5 baris kode tambahan, dua di antaranya dapat di-autogenerasi oleh Visual Studio. Setelah Anda menyiapkan kerangka kerja dependensi injeksi Anda untuk menggantikan tipe konkret untuk
IMyDependency
saat runtime - yang untuk kerangka kerja DI yang layak, adalah satu baris kode lainnya - semuanya Hanya Bekerja, kecuali sekarang Anda dapat mengejek dan dengan demikian menguji pengontrol Anda sesuai dengan isi hati Anda. .6 baris kode tambahan untuk memungkinkan pengujian ... dan kolega Anda berpendapat bahwa "terlalu banyak pekerjaan"? Argumen itu tidak cocok dengan saya, dan itu seharusnya tidak cocok dengan Anda.
Dan Anda tidak harus membuat dan mengimplementasikan antarmuka untuk pengujian: Moq , misalnya, memungkinkan Anda untuk mensimulasikan perilaku tipe konkret untuk tujuan pengujian unit. Tentu saja, itu tidak akan banyak berguna bagi Anda jika Anda tidak dapat menyuntikkan tipe-tipe itu ke dalam kelas yang Anda uji.
Injeksi ketergantungan adalah salah satu dari hal-hal yang setelah Anda memahaminya, Anda bertanya-tanya "bagaimana saya bekerja tanpa ini?". Sederhana, efektif, dan itu Masuk Akal. Tolong, jangan biarkan kolega Anda kurang memahami hal-hal baru untuk menghalangi proyek Anda diuji.
sumber
Ketika saya menulis unit test, saya mulai memikirkan apa yang bisa salah dalam kode saya. Ini membantu saya meningkatkan desain kode dan menerapkan prinsip tanggung jawab tunggal (SRP). Juga, ketika saya kembali untuk memodifikasi kode yang sama beberapa bulan kemudian, ini membantu saya untuk mengkonfirmasi bahwa fungsi yang ada tidak rusak.
Ada kecenderungan untuk menggunakan fungsi murni sebanyak yang Anda bisa (aplikasi tanpa server). Pengujian unit membantu saya mengisolasi keadaan dan menulis fungsi murni.
Tulis tes unit untuk API yang mendasarinya terlebih dahulu dan jika Anda memiliki waktu pengembangan yang cukup, Anda perlu menulis tes untuk layanan Web API yang tipis juga.
TL; DR, pengujian unit membantu meningkatkan kualitas kode dan membantu membuat perubahan di masa depan menjadi bebas risiko kode. Ini juga meningkatkan keterbacaan kode. Gunakan tes alih-alih komentar untuk menegaskan maksud Anda.
sumber
Intinya, dan apa yang seharusnya menjadi argumen Anda dengan lot yang enggan, adalah bahwa tidak ada konflik. Kesalahan besar tampaknya adalah bahwa seseorang menciptakan ide untuk "merancang pengujian" kepada orang-orang yang membenci pengujian. Mereka seharusnya hanya menutup mulut atau mengucapkannya secara berbeda, seperti "mari kita luangkan waktu untuk melakukan ini dengan benar".
Gagasan bahwa "Anda harus mengimplementasikan antarmuka" untuk membuat sesuatu dapat diuji adalah salah. Antarmuka sudah diterapkan, hanya belum dideklarasikan dalam deklarasi kelas. Ini adalah masalah mengenali metode publik yang ada, menyalin tanda tangan mereka ke antarmuka dan mendeklarasikan antarmuka itu dalam deklarasi kelas. Tidak ada pemrograman, tidak ada perubahan pada logika yang ada.
Rupanya beberapa orang memiliki ide berbeda tentang hal ini. Saya sarankan Anda mencoba untuk memperbaikinya terlebih dahulu.
sumber