Saya mencoba untuk membiasakan menulis unit test secara teratur dengan kode saya, tetapi saya telah membaca bahwa pertama - tama penting untuk menulis kode yang dapat diuji . Pertanyaan ini menyentuh prinsip-prinsip SOLID dalam penulisan kode yang dapat diuji, tetapi saya ingin tahu apakah prinsip-prinsip desain tersebut bermanfaat (atau setidaknya tidak berbahaya) tanpa berencana menulis tes sama sekali. Untuk memperjelas - Saya mengerti pentingnya tes menulis; ini bukan pertanyaan tentang kegunaannya.
Untuk mengilustrasikan kebingungan saya, pada bagian yang mengilhami pertanyaan ini, penulis memberikan contoh fungsi yang memeriksa waktu saat ini, dan mengembalikan beberapa nilai tergantung pada waktu. Penulis menunjuk ini sebagai kode buruk karena menghasilkan data (waktu) yang digunakan secara internal, sehingga membuatnya sulit untuk diuji. Namun bagi saya, sepertinya terlalu banyak menghabiskan waktu sebagai argumen. Pada titik tertentu, nilai perlu diinisialisasi, dan mengapa tidak paling dekat dengan konsumsi? Plus, tujuan metode ini dalam pikiran saya adalah mengembalikan beberapa nilai berdasarkan waktu saat ini , dengan menjadikannya parameter yang Anda maksudkan bahwa tujuan ini dapat / harus diubah. Ini, dan pertanyaan lain, membuat saya bertanya-tanya apakah kode yang dapat diuji identik dengan kode "lebih baik".
Apakah menulis kode yang dapat diuji masih merupakan praktik yang baik meskipun tidak ada tes?
Apakah kode yang dapat diuji sebenarnya lebih stabil? telah disarankan sebagai duplikat. Namun, pertanyaan itu adalah tentang "stabilitas" kode, tetapi saya bertanya lebih luas tentang apakah kode lebih unggul karena alasan lain juga, seperti keterbacaan, kinerja, penggandengan, dan sebagainya.
sumber
func(X)
kembali"Morning"
, maka mengganti semua kejadianfunc(X)
dengan"Morning"
tidak akan mengubah program (mis. Panggilanfunc
tidak melakukan apa pun selain mengembalikan nilai). Idempotency menyiratkan apakah itufunc(func(X)) == X
(yang bukan tipe-benar), atau yangfunc(X); func(X);
melakukan efek samping yang sama denganfunc(X)
(tetapi tidak ada efek samping di sini)Jawaban:
Berkenaan dengan definisi umum unit test, saya akan mengatakan tidak. Saya telah melihat kode sederhana dibuat berbelit-belit karena kebutuhan untuk memutarnya agar sesuai dengan kerangka pengujian (mis. Antarmuka dan IoC di mana-mana membuat hal-hal sulit untuk ditindaklanjuti melalui lapisan panggilan antarmuka dan data yang harus jelas diteruskan oleh sihir). Diberi pilihan antara kode yang mudah dimengerti atau kode yang mudah untuk unit test, saya selalu menggunakan kode yang bisa dipelihara setiap waktu.
Ini tidak berarti untuk tidak menguji, tetapi agar sesuai dengan alat yang sesuai dengan Anda, bukan sebaliknya. Ada beberapa cara lain untuk menguji (tetapi kode yang sulit dipahami adalah kode yang buruk). Misalnya, Anda dapat membuat tes unit yang kurang granular (mis. , Sikap Martin Fowler bahwa unit umumnya kelas, bukan metode), atau Anda dapat menekan program Anda dengan tes integrasi otomatis sebagai gantinya. Semacam itu mungkin tidak secantik kerangka pengujian Anda menyala dengan kutu hijau, tapi kami setelah kode diuji, bukan gamification proses, kan?
Anda dapat membuat kode Anda mudah dipelihara dan masih bagus untuk pengujian unit dengan mendefinisikan antarmuka yang baik di antara mereka dan kemudian menulis tes yang menggunakan antarmuka publik dari komponen; atau Anda bisa mendapatkan kerangka uji yang lebih baik (yang menggantikan fungsi saat runtime untuk mengejek mereka, daripada membutuhkan kode untuk dikompilasi dengan mengejek di tempat). Kerangka kerja pengujian unit yang lebih baik memungkinkan Anda mengganti fungsionalitas sistem GetCurrentTime () dengan fungsi Anda sendiri, saat runtime, sehingga Anda tidak perlu memperkenalkan pembungkus buatan untuk ini hanya agar sesuai dengan alat uji.
sumber
Hal pertama yang pertama, tidak adanya tes adalah masalah yang jauh lebih besar daripada kode Anda diuji atau tidak. Tidak memiliki unit test berarti Anda tidak selesai dengan kode / fitur Anda.
Itu keluar dari jalan, saya tidak akan mengatakan bahwa penting untuk menulis kode yang dapat diuji - penting untuk menulis kode yang fleksibel . Kode yang tidak fleksibel sulit untuk diuji, jadi ada banyak tumpang tindih dalam pendekatan dan apa yang orang sebut.
Jadi bagi saya, selalu ada seperangkat prioritas dalam penulisan kode:
Tes unit membantu menjaga kode, tetapi hanya sampai titik tertentu. Jika Anda membuat kode lebih mudah dibaca, atau lebih rapuh untuk membuat unit test berfungsi, itu menjadi kontra produktif. "Kode yang dapat diuji" umumnya adalah kode yang fleksibel, jadi itu bagus, tetapi tidak sepenting fungsi atau perawatan. Untuk sesuatu seperti waktu saat ini, membuat fleksibel itu bagus tetapi membahayakan perawatan dengan membuat kode lebih sulit untuk digunakan dengan benar dan lebih kompleks. Karena pemeliharaan lebih penting, saya biasanya akan melakukan kesalahan terhadap pendekatan yang lebih sederhana bahkan jika itu kurang teruji.
sumber
Ya, itu praktik yang baik. Alasannya adalah bahwa testability bukan untuk kepentingan tes. Demi kejelasan dan pengertian yang dibawanya.
Tidak ada yang peduli dengan tes itu sendiri. Adalah kenyataan hidup yang menyedihkan bahwa kita memerlukan rangkaian uji regresi besar karena kita tidak cukup cemerlang untuk menulis kode sempurna tanpa terus-menerus memeriksa pijakan kita. Jika kita bisa, konsep tes tidak akan diketahui, dan semua ini tidak akan menjadi masalah. Saya tentu berharap saya bisa. Tetapi pengalaman menunjukkan bahwa hampir semua dari kita tidak bisa, oleh karena itu tes yang mencakup kode kita adalah hal yang baik bahkan jika mereka mengambil waktu dari penulisan kode bisnis.
Bagaimana memiliki tes meningkatkan kode bisnis kita secara independen dari tes itu sendiri? Dengan memaksa kami untuk membagi fungsi kami ke dalam unit yang dengan mudah ditunjukkan benar. Unit-unit ini juga lebih mudah untuk diperbaiki daripada yang kita akan tergoda untuk menulis.
Contoh waktu Anda adalah poin yang bagus. Selama Anda hanya memiliki fungsi mengembalikan waktu saat ini Anda mungkin berpikir bahwa tidak ada gunanya memprogramnya. Seberapa sulit untuk melakukan ini dengan benar? Tapi pasti program anda akan menggunakan fungsi ini dalam kode lain, dan Anda pasti ingin menguji bahwa kode di bawah kondisi yang berbeda, termasuk pada waktu yang berbeda. Oleh karena itu ide yang baik untuk dapat memanipulasi waktu fungsi Anda kembali - bukan karena Anda tidak mempercayai
currentMillis()
panggilan satu-line Anda , tetapi karena Anda perlu memverifikasi penelepon dari panggilan itu dalam keadaan terkendali. Jadi Anda tahu, memiliki kode yang dapat diuji sangat berguna bahkan jika dengan sendirinya, tampaknya tidak terlalu menarik perhatian.sumber
Nobody cares about the tests themselves
- saya lakukan. Saya menemukan tes sebagai dokumentasi yang lebih baik dari apa yang kode lakukan daripada komentar atau file readme.Karena Anda mungkin perlu menggunakan kembali kode itu, dengan nilai yang berbeda dari yang dihasilkan secara internal. Kemampuan untuk memasukkan nilai yang akan Anda gunakan sebagai parameter, memastikan bahwa Anda dapat menghasilkan nilai-nilai berdasarkan kapan saja Anda suka, bukan hanya "sekarang" (dengan "sekarang" yang berarti ketika Anda memanggil kode).
Membuat kode dapat diuji pada dasarnya berarti membuat kode yang dapat (sejak awal) digunakan dalam dua skenario berbeda (produksi dan pengujian).
Pada dasarnya, walaupun Anda dapat berdebat bahwa tidak ada insentif untuk membuat kode dapat diuji tanpa adanya tes, ada keuntungan besar dalam menulis kode yang dapat digunakan kembali, dan keduanya adalah sinonim.
Anda juga bisa berpendapat bahwa tujuan dari metode ini adalah mengembalikan beberapa nilai berdasarkan nilai waktu, dan Anda memerlukannya untuk menghasilkan nilai berdasarkan "sekarang". Salah satunya lebih fleksibel, dan jika Anda terbiasa memilih varian itu, pada waktunya, tingkat penggunaan kembali kode Anda akan naik.
sumber
Tampaknya konyol untuk mengatakannya seperti ini, tetapi jika Anda ingin dapat menguji kode Anda, maka ya, menulis kode yang dapat diuji lebih baik. Anda bertanya:
Justru karena, dalam contoh yang Anda maksudkan, itu membuat kode itu tidak dapat diuji. Kecuali jika Anda hanya menjalankan sebagian tes pada waktu yang berbeda dalam sehari. Atau Anda mengatur ulang jam sistem. Atau solusi lain. Semuanya lebih buruk daripada hanya membuat kode Anda fleksibel.
Selain tidak fleksibel, metode kecil tersebut memiliki dua tanggung jawab: (1) mendapatkan waktu sistem dan kemudian (2) mengembalikan beberapa nilai berdasarkan itu.
Masuk akal untuk memecah tanggung jawab lebih lanjut, sehingga bagian di luar kendali Anda (
DateTime.Now
) memiliki dampak paling kecil pada sisa kode Anda. Melakukan hal itu akan membuat kode di atas lebih sederhana, dengan efek samping yang dapat diuji secara sistematis.sumber
Memang ada biaya, tetapi beberapa pengembang begitu terbiasa membayarnya sehingga mereka lupa biayanya ada. Sebagai contoh, Anda sekarang memiliki dua unit alih-alih satu, Anda memerlukan kode panggilan untuk menginisialisasi dan mengelola ketergantungan tambahan, dan sementara
GetTimeOfDay
lebih dapat diuji, Anda segera kembali ke perahu yang sama menguji baru AndaIDateTimeProvider
. Hanya saja jika Anda memiliki tes yang baik, manfaatnya biasanya lebih besar daripada biayanya.Juga, pada tingkat tertentu, menulis kode yang dapat diuji mendorong Anda untuk merancang kode Anda dengan cara yang lebih mudah dikelola. Kode manajemen dependensi baru ini mengganggu, jadi Anda harus mengelompokkan semua fungsi yang tergantung waktu bersama-sama, jika memungkinkan. Itu dapat membantu mengurangi dan memperbaiki bug seperti, misalnya, ketika Anda memuat halaman tepat pada batas waktu, membuat beberapa elemen dirender menggunakan waktu sebelum dan beberapa menggunakan waktu sesudah. Ini juga dapat mempercepat program Anda dengan menghindari panggilan sistem berulang untuk mendapatkan waktu saat ini.
Tentu saja, perbaikan arsitektur tersebut sangat tergantung pada seseorang yang memperhatikan peluang dan mengimplementasikannya. Salah satu bahaya terbesar dari memusatkan perhatian begitu erat pada unit adalah kehilangan pandangan dari gambaran yang lebih besar.
Banyak kerangka kerja unit test memungkinkan Anda menambal objek tiruan pada saat runtime, yang memungkinkan Anda mendapatkan manfaat dari testabilitas tanpa semua kekacauan. Saya bahkan pernah melihatnya di C ++. Lihatlah kemampuan itu dalam situasi di mana tampaknya biaya pengujian tidak sepadan.
sumber
Ada kemungkinan bahwa tidak semua karakteristik yang berkontribusi terhadap testabilitas diinginkan di luar konteks testability - Saya mengalami masalah dengan pembenaran terkait non-tes untuk parameter waktu yang Anda sebutkan, misalnya - tetapi secara umum karakteristik yang berkontribusi terhadap testability juga berkontribusi pada kode yang baik terlepas dari testabilitas.
Secara umum, kode yang dapat diuji adalah kode yang dapat ditempa. Ini dalam potongan kecil, diskrit, kohesif, sehingga bit individu dapat dipanggil untuk digunakan kembali. Ini terorganisasi dengan baik dan diberi nama baik (untuk dapat menguji beberapa fungsionalitas Anda memberikan lebih banyak perhatian pada penamaan; jika Anda tidak menulis tes, nama untuk fungsi sekali pakai akan kurang penting). Itu cenderung lebih parametrik (seperti contoh waktu Anda), jadi terbuka untuk digunakan dari konteks lain daripada tujuan yang dimaksudkan. KERING, jadi kurang berantakan dan lebih mudah dipahami.
Iya. Merupakan praktik yang baik untuk menulis kode yang dapat diuji, meskipun terlepas dari pengujian.
sumber
Menulis kode yang dapat diuji sangat penting jika Anda ingin dapat membuktikan bahwa kode Anda benar-benar berfungsi.
Saya cenderung setuju dengan sentimen negatif tentang membengkokkan kode Anda ke dalam contortions yang keji hanya agar sesuai dengan kerangka uji tertentu.
Di sisi lain, semua orang di sini, pada satu titik atau lain, harus berurusan dengan fungsi sulap panjang 1.000 baris yang hanya harus ditangani, hampir tidak dapat disentuh tanpa melanggar satu atau lebih yang tidak jelas, tidak ketergantungan yang jelas di tempat lain (atau di suatu tempat di dalam dirinya sendiri, di mana ketergantungan hampir tidak mungkin untuk divisualisasikan) dan cukup banyak dengan definisi yang tidak dapat disembuhkan. Gagasan (yang bukan tanpa prestasi) bahwa kerangka kerja pengujian telah menjadi berlebihan tidak boleh dianggap sebagai lisensi gratis untuk menulis kualitas buruk, kode yang tidak dapat diuji, menurut pendapat saya.
Cita-cita pembangunan yang digerakkan oleh tes cenderung mendorong Anda untuk menulis prosedur tanggung jawab tunggal, misalnya, dan itu jelas merupakan hal yang baik. Secara pribadi, saya katakan beli menjadi tanggung jawab tunggal, sumber kebenaran tunggal, lingkup terkontrol (tidak ada variabel global freakin ') dan menjaga ketergantungan rapuh seminimal mungkin, dan kode Anda akan dapat diuji. Diuji oleh beberapa kerangka kerja pengujian tertentu? Siapa tahu. Tapi mungkin itu kerangka pengujian yang perlu menyesuaikan diri dengan kode yang baik, dan bukan sebaliknya.
Tetapi hanya untuk memperjelas, kode yang sangat pintar, atau sangat panjang dan / atau saling tergantung sehingga tidak mudah dipahami oleh manusia lain bukanlah kode yang baik. Dan itu juga, secara kebetulan, bukan kode yang dapat dengan mudah diuji.
Jadi mendekati ringkasan saya, apakah kode yang dapat diuji adalah kode yang lebih baik ?
Saya tidak tahu, mungkin juga tidak. Orang-orang di sini memiliki beberapa poin yang valid.
Tetapi saya percaya bahwa kode yang lebih baik cenderung juga merupakan kode yang dapat diuji .
Dan bahwa jika Anda berbicara tentang perangkat lunak serius untuk digunakan dalam upaya serius, pengiriman kode yang belum diuji bukanlah hal yang paling bertanggung jawab yang dapat Anda lakukan dengan uang atasan Anda atau pelanggan Anda.
Memang benar bahwa beberapa kode memerlukan pengujian yang lebih ketat daripada kode lainnya dan agak konyol untuk berpura-pura sebaliknya. Bagaimana Anda ingin menjadi astronot di pesawat ulang-alik jika sistem menu yang menghubungkan Anda dengan sistem vital pada pesawat ulang-alik tidak diuji? Atau seorang karyawan di pabrik nuklir di mana sistem perangkat lunak yang memonitor suhu di reaktor tidak diuji? Di sisi lain, apakah sedikit kode yang menghasilkan laporan hanya-baca sederhana memerlukan truk kontainer penuh dengan dokumentasi dan seribu tes unit? Saya harap tidak. Hanya mengatakan ...
sumber
Anda benar, dan dengan mengejek Anda dapat membuat kode dapat diuji dan menghindari waktu (pun niat tidak ditentukan). Kode contoh:
Sekarang katakanlah Anda ingin menguji apa yang terjadi selama detik kabisat. Seperti yang Anda katakan, untuk menguji ini dengan cara yang berlebihan Anda harus mengubah kode (produksi):
Jika Python mendukung detik kabisat , kode tes akan terlihat seperti ini:
Anda dapat menguji ini, tetapi kode lebih kompleks dari yang diperlukan dan tes masih tidak dapat diandalkan menjalankan cabang kode yang sebagian besar kode produksi akan menggunakan (yaitu, tidak melewati nilai untuk
now
). Anda mengatasinya dengan menggunakan tiruan . Mulai dari kode produksi asli:Kode uji:
Ini memberikan beberapa manfaat:
time_of_day
secara independen .Di samping catatan, diharapkan bahwa kerangka kerja mengejek di masa depan akan membuat hal-hal seperti ini lebih mudah. Misalnya, karena Anda harus merujuk ke fungsi mengejek sebagai string, Anda tidak dapat dengan mudah membuat IDE mengubahnya secara otomatis ketika
time_of_day
mulai menggunakan sumber lain untuk waktu.sumber
Kualitas kode yang ditulis dengan baik adalah bahwa ia kuat untuk diubah . Artinya, ketika perubahan persyaratan datang, perubahan dalam kode harus proporsional. Ini adalah yang ideal (dan tidak selalu tercapai), tetapi menulis kode yang dapat diuji membantu membuat kita lebih dekat ke tujuan ini.
Mengapa itu membantu membuat kita lebih dekat? Dalam produksi, kode kami beroperasi dalam lingkungan produksi, termasuk mengintegrasikan dan berinteraksi dengan semua kode kami yang lain. Dalam pengujian unit, kami menyapu banyak lingkungan ini. Kode kami sekarang sedang kuat untuk diubah karena tes adalah perubahan . Kami menggunakan unit dengan cara yang berbeda, dengan input yang berbeda (mengolok-olok, input buruk yang mungkin tidak pernah benar-benar dilewati, dll) daripada kita akan menggunakannya dalam produksi.
Ini mempersiapkan kode kami untuk hari ketika perubahan terjadi di sistem kami. Katakanlah perhitungan waktu kita perlu waktu yang berbeda berdasarkan zona waktu. Sekarang kami memiliki kemampuan untuk lulus dalam waktu itu dan tidak harus membuat perubahan apa pun pada kode. Ketika kita tidak ingin melewatkan waktu dan ingin menggunakan waktu saat ini, kita bisa menggunakan argumen default. Kode kami kuat untuk diubah karena dapat diuji.
sumber
Dari pengalaman saya, salah satu keputusan paling penting dan paling luas yang Anda buat ketika membangun sebuah program adalah bagaimana Anda memecah kode menjadi unit (di mana "unit" digunakan dalam arti luas). Jika Anda menggunakan bahasa OO berbasis kelas, Anda perlu memecah semua mekanisme internal yang digunakan untuk mengimplementasikan program ke beberapa kelas. Maka Anda perlu memecah kode masing-masing kelas menjadi beberapa metode. Dalam beberapa bahasa, pilihannya adalah bagaimana memecah kode Anda menjadi fungsi. Atau jika Anda melakukan hal SOA, Anda perlu memutuskan berapa banyak layanan yang akan Anda bangun dan apa yang akan masuk ke setiap layanan.
Kerusakan yang Anda pilih memiliki efek besar pada keseluruhan proses. Pilihan yang baik membuat kode lebih mudah untuk ditulis, dan menghasilkan lebih sedikit bug (bahkan sebelum Anda memulai pengujian dan debugging). Mereka membuatnya lebih mudah untuk berubah dan mempertahankan. Menariknya, ternyata begitu Anda menemukan kerusakan yang baik, biasanya juga lebih mudah untuk menguji daripada yang buruk.
Kenapa begitu? Saya rasa saya tidak bisa mengerti dan menjelaskan semua alasan. Tetapi salah satu alasannya adalah bahwa rincian yang baik selalu berarti memilih "ukuran butir" yang moderat untuk unit implementasi. Anda tidak ingin menjejalkan terlalu banyak fungsionalitas dan terlalu banyak logika ke dalam satu kelas / metode / fungsi / modul / dll. Ini membuat kode Anda lebih mudah dibaca dan lebih mudah ditulis, tetapi juga membuatnya lebih mudah untuk diuji.
Bukan hanya itu saja. Desain internal yang baik berarti bahwa perilaku yang diharapkan (input / output / dll) dari setiap unit implementasi dapat didefinisikan dengan jelas dan tepat. Ini penting untuk pengujian. Desain yang baik biasanya berarti bahwa setiap unit implementasi akan memiliki jumlah dependensi yang moderat. Itu membuat kode Anda lebih mudah dibaca dan dipahami orang lain, tetapi juga membuatnya lebih mudah untuk diuji. Alasannya berlanjut; mungkin orang lain dapat mengartikulasikan lebih banyak alasan yang saya tidak bisa.
Berkenaan dengan contoh dalam pertanyaan Anda, saya tidak berpikir bahwa "desain kode yang baik" sama dengan mengatakan bahwa semua dependensi eksternal (seperti ketergantungan pada jam sistem) harus selalu "disuntikkan". Itu mungkin ide yang bagus, tetapi ini adalah masalah terpisah dari apa yang saya jelaskan di sini dan saya tidak akan mempelajari pro dan kontra.
Kebetulan, bahkan jika Anda membuat panggilan langsung ke fungsi sistem yang mengembalikan waktu saat ini, bertindak pada sistem file, dan sebagainya, ini tidak berarti Anda tidak dapat menguji unit kode Anda secara terpisah. Caranya adalah dengan menggunakan versi khusus dari perpustakaan standar yang memungkinkan Anda untuk memalsukan nilai-nilai kembali fungsi sistem. Saya belum pernah melihat orang lain menyebutkan teknik ini, tetapi cukup sederhana untuk dilakukan dengan banyak bahasa dan platform pengembangan. (Semoga runtime bahasa Anda bersifat open-source dan mudah dibuat. Jika mengeksekusi kode Anda melibatkan langkah tautan, mudah-mudahan juga mudah untuk mengontrol perpustakaan mana yang terhubung dengannya.)
Singkatnya, kode yang dapat diuji tidak selalu merupakan kode "baik", tetapi kode "baik" biasanya dapat diuji.
sumber
Jika Anda menggunakan prinsip-prinsip SOLID Anda akan berada di sisi yang baik, terutama jika memperpanjang ini dengan KISS , KERING , dan YAGNI .
Satu poin yang hilang bagi saya adalah titik kompleksitas suatu metode. Apakah ini metode pengambil / penyetel yang sederhana? Maka hanya menulis tes untuk memuaskan kerangka pengujian Anda akan membuang-buang waktu.
Jika ini adalah metode yang lebih kompleks di mana Anda memanipulasi data dan ingin memastikan bahwa itu akan berfungsi bahkan jika Anda harus mengubah logika internal, maka itu akan menjadi panggilan yang bagus untuk metode pengujian. Sering kali saya harus mengubah kode setelah beberapa hari / minggu / bulan, dan saya sangat senang memiliki test case. Ketika pertama kali mengembangkan metode saya mengujinya dengan metode tes, dan saya yakin itu akan berhasil. Setelah perubahan kode tes utama saya masih berfungsi. Jadi saya yakin perubahan saya tidak merusak kode lama dalam produksi.
Aspek lain dari tes menulis adalah menunjukkan kepada pengembang lain cara menggunakan metode Anda. Berkali-kali pengembang akan mencari contoh tentang cara menggunakan metode dan berapa nilai pengembaliannya.
Hanya dua sen saya .
sumber