Saya biasanya mencoba mengikuti saran buku Bekerja Efektif dengan Legacy Cod e . Saya memecahkan dependensi, memindahkan bagian kode ke @VisibleForTesting public static
metode dan ke kelas baru untuk membuat kode (atau setidaknya beberapa bagian darinya) dapat diuji. Dan saya menulis tes untuk memastikan bahwa saya tidak merusak apa pun ketika saya memodifikasi atau menambahkan fungsi baru.
Seorang kolega mengatakan bahwa saya tidak boleh melakukan ini. Alasannya:
- Kode asli mungkin tidak berfungsi dengan baik sejak awal. Dan menulis tes untuk itu membuat perbaikan dan modifikasi di masa depan lebih sulit karena devs harus memahami dan memodifikasi tes juga.
- Jika itu kode GUI dengan beberapa logika (~ 12 baris, 2-3 jika / selain itu blok, misalnya), tes tidak sebanding dengan masalahnya karena kode tersebut terlalu sepele untuk memulai.
- Pola buruk yang serupa bisa ada di bagian lain dari basis kode juga (yang belum saya lihat, saya agak baru); akan lebih mudah untuk membersihkan semuanya dalam satu refactoring besar. Mengekstrak logika bisa merusak kemungkinan di masa depan ini.
Haruskah saya menghindari mengeluarkan bagian yang dapat diuji dan menulis tes jika kita tidak punya waktu untuk menyelesaikan refactoring? Apakah ada kerugian untuk hal ini yang harus saya pertimbangkan?
Jawaban:
Inilah kesan pribadi saya yang tidak ilmiah: ketiga alasan itu terdengar seperti ilusi kognitif yang tersebar luas tetapi salah.
sumber
Beberapa pemikiran:
Saat Anda refactoring kode lawas, tidak masalah jika beberapa tes yang Anda tulis kebetulan bertentangan dengan spesifikasi ideal. Yang penting adalah mereka menguji perilaku program saat ini . Refactoring adalah tentang mengambil langkah-langkah iso-fungsional kecil untuk membuat kode lebih bersih; Anda tidak ingin terlibat dalam perbaikan bug saat Anda melakukan refactoring. Selain itu, jika Anda menemukan bug yang mencolok, itu tidak akan hilang. Anda selalu dapat menulis tes regresi untuk itu dan menonaktifkannya sementara, atau menyisipkan tugas perbaikan bug di backlog Anda untuk nanti. Satu hal dalam satu waktu.
Saya setuju bahwa kode GUI murni sulit untuk diuji dan mungkin tidak cocok untuk " Bekerja Secara Efektif ... " - gaya refactoring. Namun, ini tidak berarti Anda tidak harus mengekstrak perilaku yang tidak ada hubungannya di lapisan GUI dan menguji kode yang diekstraksi. Dan "12 baris, 2-3 jika / blok lain" tidak sepele. Semua kode dengan setidaknya sedikit logika kondisional harus diuji.
Dalam pengalaman saya, refactoring besar tidak mudah dan mereka jarang bekerja. Jika Anda tidak menetapkan diri Anda sendiri dengan tepat, tujuan-tujuan kecil, ada risiko tinggi bahwa Anda memulai pengerjaan ulang yang tidak pernah berakhir, menarik rambut di mana Anda tidak akan pernah mendarat di kaki Anda pada akhirnya. Semakin besar perubahan, semakin Anda berisiko melanggar sesuatu dan semakin banyak kesulitan yang Anda miliki untuk mengetahui di mana Anda gagal.
Membuat segalanya menjadi lebih baik secara pro hoc dengan refactor kecil tidak "merongrong kemungkinan masa depan", itu memungkinkan mereka - memperkuat tanah berawa di mana aplikasi Anda berada. Anda pasti harus melakukannya.
sumber
Juga ulang: "Kode asli mungkin tidak berfungsi dengan baik" - itu tidak berarti Anda hanya mengubah perilaku kode tanpa khawatir tentang dampaknya. Kode lain mungkin mengandalkan perilaku yang tampaknya rusak, atau efek samping dari implementasi saat ini. Cakupan uji coba aplikasi yang ada seharusnya membuatnya lebih mudah untuk refactor nanti, karena itu akan membantu Anda mengetahui ketika Anda secara tidak sengaja memecahkan sesuatu. Anda harus menguji bagian terpenting terlebih dahulu.
sumber
Jawaban Kilian mencakup aspek yang paling penting, tetapi saya ingin memperluas poin 1 dan 3.
Jika pengembang ingin mengubah kode (refactor, extended, debug), ia harus memahaminya. Dia harus memastikan perubahannya memengaruhi perilaku yang diinginkannya (tidak ada dalam hal refactoring), dan tidak ada yang lain.
Jika ada tes, maka dia harus memahami tes juga, tentu. Pada saat yang sama, tes harus membantunya memahami kode utama, dan tes jauh lebih mudah dipahami daripada kode fungsional (kecuali mereka tes yang buruk). Dan tes membantu menunjukkan apa yang berubah dalam perilaku kode lama. Bahkan jika kode asli salah, dan tes menguji perilaku salah itu, itu masih merupakan keuntungan.
Namun, ini mengharuskan tes didokumentasikan sebagai pengujian perilaku yang sudah ada, bukan spesifikasi.
Beberapa pemikiran pada poin 3 juga: selain fakta bahwa "big swoop" jarang pernah benar-benar terjadi, ada juga hal lain: itu sebenarnya tidak mudah. Agar lebih mudah, beberapa kondisi harus diterapkan:
XYZSingleton
? Apakah pengambil contoh mereka selalu dipanggilgetInstance()
? Dan bagaimana Anda menemukan hierarki Anda yang terlalu dalam? Bagaimana Anda mencari benda dewa Anda? Ini memerlukan analisis metrik kode dan kemudian memeriksa metrik secara manual. Atau Anda hanya tersandung saat mereka bekerja, seperti yang Anda lakukan.sumber
Ada budaya di beberapa perusahaan di mana mereka segan untuk memungkinkan pengembang kapan saja untuk meningkatkan kode yang tidak secara langsung memberikan nilai tambahan misalnya fungsi baru.
Saya mungkin berkhotbah kepada orang-orang yang bertobat di sini, tetapi itu jelas merupakan ekonomi palsu. Kode yang bersih dan ringkas bermanfaat bagi pengembang selanjutnya. Hanya saja pengembaliannya tidak segera terbukti.
Saya pribadi berlangganan Prinsip Pramuka tetapi yang lain (seperti yang sudah Anda lihat) tidak.
Yang mengatakan, perangkat lunak menderita entropi dan membangun utang teknis. Pengembang sebelumnya yang kekurangan waktu (atau mungkin hanya malas atau tidak berpengalaman) mungkin telah menerapkan solusi kereta sub-optimal daripada yang dirancang dengan baik. Meskipun tampaknya diinginkan untuk memperbaiki ini, Anda berisiko memperkenalkan bug baru pada kode kerja (bagi pengguna).
Beberapa perubahan berisiko lebih rendah daripada yang lain. Misalnya, tempat saya bekerja cenderung ada banyak kode duplikat yang dapat dengan aman dipindahkan ke subrutin dengan dampak minimal.
Pada akhirnya, Anda harus membuat penilaian untuk seberapa jauh Anda mengambil refactoring tetapi ada nilai yang tidak dapat disangkal dalam menambahkan tes otomatis jika belum ada.
sumber
Dalam pengalaman saya, tes karakterisasi semacam bekerja dengan baik. Ini memberi Anda cakupan tes yang luas tapi tidak terlalu spesifik relatif cepat, tetapi bisa sulit untuk diterapkan untuk aplikasi GUI.
Saya kemudian akan menulis unit test untuk bagian-bagian yang ingin Anda ubah dan melakukannya setiap kali Anda ingin melakukan perubahan sehingga meningkatkan cakupan tes unit Anda dari waktu ke waktu.
Pendekatan ini memberi Anda ide yang baik jika perubahan mempengaruhi bagian lain dari sistem dan mari Anda masuk ke posisi untuk melakukan perubahan yang diperlukan lebih awal.
sumber
Re: "Kode asli mungkin tidak berfungsi dengan baik":
Tes tidak ditulis dalam batu. Mereka bisa diubah. Dan jika Anda menguji fitur yang salah, seharusnya mudah untuk menulis ulang tes dengan lebih benar. Bagaimanapun, hanya hasil yang diharapkan dari fungsi yang diuji yang harus berubah.
sumber
Baiklah. Menjawab sebagai insinyur uji perangkat lunak. Pertama, Anda harus menguji semua yang pernah Anda lakukan. Karena jika tidak, Anda tidak tahu apakah itu berfungsi atau tidak. Ini mungkin tampak jelas bagi kami, tetapi saya memiliki rekan yang melihatnya secara berbeda. Sekalipun proyek Anda adalah proyek kecil yang mungkin tidak pernah dikirimkan, Anda harus menatap wajah pengguna dan mengatakan Anda tahu itu berhasil karena Anda mengujinya.
Kode non-sepele selalu mengandung bug (mengutip seorang pria dari uni; dan jika tidak ada bug di dalamnya, itu sepele) dan tugas kami adalah menemukannya sebelum pelanggan melakukannya. Kode lama memiliki bug lama. Jika kode asli tidak berfungsi sebagaimana mestinya, Anda ingin mengetahuinya, percayalah. Bug tidak masalah jika Anda mengetahuinya, jangan takut untuk menemukannya, itulah gunanya catatan rilis.
Jika saya ingat benar buku Refactoring mengatakan untuk menguji terus-menerus, jadi itu bagian dari proses.
sumber
Lakukan cakupan tes otomatis.
Waspadai angan-angan, baik Anda sendiri maupun oleh pelanggan dan bos Anda. Saya sangat ingin percaya bahwa perubahan saya akan benar untuk pertama kalinya dan saya hanya perlu menguji sekali, saya telah belajar untuk memperlakukan pemikiran seperti itu dengan cara yang sama saya memperlakukan email penipuan Nigeria. Yah, kebanyakan; Saya tidak pernah mencari email penipuan tetapi baru-baru ini (ketika diteriaki) saya menyerah karena tidak menggunakan praktik terbaik. Itu adalah pengalaman menyakitkan yang menyeret (mahal) terus menerus. Tidak akan lagi!
Saya memiliki kutipan favorit dari komik web Freefall: "Apakah Anda pernah bekerja di bidang yang kompleks di mana penyelia hanya memiliki gambaran kasar tentang perincian teknis? ... Maka Anda tahu cara paling pasti untuk menyebabkan penyelia Anda gagal adalah dengan ikuti setiap perintahnya tanpa pertanyaan. "
Mungkin tepat untuk membatasi jumlah waktu Anda berinvestasi.
sumber
Jika Anda berurusan dengan kode legacy dalam jumlah besar yang saat ini tidak diuji, dapatkan cakupan tes sekarang alih-alih menunggu penulisan ulang hipotetis besar di masa depan adalah langkah yang tepat. Mulai dengan menulis tes unit tidak.
Tanpa pengujian otomatis, setelah melakukan perubahan pada kode Anda perlu melakukan beberapa pengujian ujung ke ujung aplikasi secara manual untuk memastikan itu berfungsi. Mulailah dengan menulis tes integrasi tingkat tinggi untuk menggantikannya. Jika aplikasi Anda membaca file, memvalidasinya, memproses data dengan cara tertentu, dan menampilkan hasil tes yang Anda inginkan yang menangkap semua itu.
Idealnya Anda akan memiliki data dari rencana pengujian manual atau bisa mendapatkan sampel data produksi aktual untuk digunakan. Jika tidak, karena aplikasi sedang dalam produksi, dalam banyak kasus itu melakukan apa yang seharusnya, jadi hanya membuat data yang akan mencapai semua poin tinggi dan menganggap output sudah benar untuk saat ini. Ini tidak lebih buruk daripada mengambil fungsi kecil, dengan asumsi itu melakukan apa yang namanya atau komentar menyarankan itu harus dilakukan, dan menulis tes dengan asumsi itu berfungsi dengan benar.
Setelah Anda mendapatkan cukup dari tes tingkat tinggi ini yang ditulis untuk menangkap operasi normal aplikasi dan kasus kesalahan yang paling umum jumlah waktu yang Anda harus habiskan menggedor keyboard untuk mencoba dan menangkap kesalahan dari kode melakukan sesuatu selain dari apa Anda pikir itu seharusnya dilakukan akan turun secara signifikan membuat refactoring masa depan (atau bahkan penulisan ulang besar) jauh lebih mudah.
Karena Anda dapat memperluas cakupan tes unit, Anda dapat pare down atau bahkan pensiun sebagian besar tes integrasi. Jika aplikasi Anda membaca / menulis file atau mengakses DB, menguji bagian-bagian itu secara terpisah dan mengejeknya atau meminta pengujian Anda dimulai dengan membuat struktur data yang dibaca dari file / database adalah tempat yang jelas untuk memulai. Sebenarnya menciptakan infrastruktur pengujian akan memakan waktu lebih lama daripada menulis serangkaian tes cepat dan kotor; dan setiap kali Anda menjalankan tes integrasi 2 menit alih-alih menghabiskan waktu 30 menit secara manual menguji sebagian kecil dari apa yang dicakup tes integrasi, Anda sudah mendapatkan kemenangan besar.
sumber