Dalam pertanyaan lain, terungkap bahwa salah satu kesulitan dengan TDD adalah menjaga suite pengujian tetap sinkron dengan basis kode selama dan setelah refactoring.
Sekarang, saya penggemar berat refactoring. Saya tidak akan menyerah untuk melakukan TDD. Tetapi saya juga pernah mengalami masalah tes yang ditulis sedemikian rupa sehingga refactoring minor menyebabkan banyak kegagalan tes.
Bagaimana Anda menghindari melanggar tes saat refactoring?
- Apakah Anda menulis tes 'lebih baik'? Jika demikian, apa yang harus Anda cari?
- Apakah Anda menghindari jenis refactoring tertentu?
- Apakah ada alat tes-refactoring?
Sunting: Saya menulis pertanyaan baru yang menanyakan apa yang ingin saya tanyakan (tetapi menjadikan ini sebagai varian yang menarik).
development-process
tdd
refactoring
Alex Feinman
sumber
sumber
Jawaban:
Apa yang Anda coba lakukan sebenarnya bukan refactoring. Dengan refactoring, menurut definisi, Anda tidak mengubah apa yang dilakukan perangkat lunak Anda, Anda mengubah cara melakukannya.
Mulailah dengan semua tes hijau (semua lulus), kemudian buat modifikasi "di bawah tenda" (misalnya memindahkan metode dari kelas turunan ke basis, mengekstrak metode, atau merangkum Komposit dengan Builder , dll.). Tes Anda harus tetap lulus.
Apa yang Anda uraikan tampaknya bukan refactoring, tetapi desain ulang, yang juga menambah fungsionalitas perangkat lunak Anda yang sedang diuji. TDD dan refactoring (seperti yang saya coba mendefinisikannya di sini) tidak bertentangan Anda masih dapat melakukan refactor (hijau-hijau) dan menerapkan TDD (merah-hijau) untuk mengembangkan fungsionalitas "delta".
sumber
Salah satu manfaat dari memiliki unit test adalah agar Anda dapat dengan yakin melakukan refactor.
Jika refactoring tidak mengubah antarmuka publik maka Anda meninggalkan tes unit apa adanya dan memastikan setelah refactoring mereka semua lulus.
Jika refactoring mengubah antarmuka publik maka tes harus ditulis ulang terlebih dahulu. Refactor sampai tes baru berlalu.
Saya tidak akan pernah menghindari refactoring karena itu merusak tes. Tes unit menulis bisa menjadi sakit di pantat tetapi nilainya sakit dalam jangka panjang.
sumber
Berlawanan dengan jawaban yang lain, penting untuk dicatat bahwa beberapa cara pengujian dapat menjadi rapuh ketika sistem yang diuji (SUT) di-refactored, jika pengujiannya adalah whitebox.
Jika saya menggunakan kerangka kerja mengejek yang memverifikasi urutan metode yang dipanggil pada tiruan (ketika urutan tidak relevan karena panggilan bebas efek samping); maka jika kode saya lebih bersih dengan panggilan metode tersebut dalam urutan yang berbeda dan saya refactor, maka pengujian saya akan rusak. Secara umum, mengejek dapat menyebabkan kerapuhan pada tes.
Jika saya memeriksa keadaan internal SUT saya dengan mengekspos anggota pribadi atau yang dilindungi (kita bisa menggunakan "teman" dalam visual basic, atau meningkatkan tingkat akses "internal" dan menggunakan "internalsvisibleto" dalam c #; dalam banyak bahasa OO, termasuk banyak c # a " test-specific-subclass " dapat digunakan) lalu tiba-tiba keadaan internal kelas akan menjadi masalah - Anda mungkin akan refactoring kelas sebagai kotak hitam, tetapi tes kotak putih akan gagal. Misalkan satu bidang digunakan kembali untuk mengartikan hal yang berbeda (bukan praktik yang baik!) Ketika SUT berubah status - jika kita membaginya menjadi dua bidang, kita mungkin perlu menulis ulang tes yang rusak.
Subclass uji-spesifik juga dapat digunakan untuk menguji metode yang dilindungi - yang dapat berarti bahwa refactor dari sudut pandang kode produksi merupakan perubahan besar dari sudut pandang kode uji. Memindahkan beberapa baris ke dalam atau keluar dari metode yang dilindungi mungkin tidak memiliki efek samping produksi, tetapi hancurkan tes.
Jika saya menggunakan " kait uji " atau kode kompilasi khusus uji atau kondisional lainnya, mungkin sulit untuk memastikan bahwa tes tidak rusak karena ketergantungan yang rapuh pada logika internal.
Jadi untuk mencegah agar tes tidak digabungkan dengan detail internal SUT yang intim, mungkin membantu untuk:
Semua poin di atas adalah contoh kopling kotak putih yang digunakan dalam pengujian. Jadi untuk benar-benar menghindari tes melanggar refactoring, gunakan pengujian kotak hitam SUT.
Penafian: Untuk tujuan membahas refactoring di sini, saya menggunakan kata ini sedikit lebih luas untuk memasukkan perubahan implementasi internal tanpa efek eksternal yang terlihat. Beberapa puritan mungkin tidak setuju dan merujuk secara eksklusif ke buku Martin Fowler dan Kent Beck Refactoring - yang menggambarkan operasi atom refactoring.
Dalam praktiknya, kami cenderung mengambil langkah-langkah tanpa-melanggar yang sedikit lebih besar daripada operasi atom yang dijelaskan di sana, dan khususnya perubahan yang membuat kode produksi berperilaku identik dari luar mungkin tidak meninggalkan tes yang lulus. Tapi saya pikir itu adil untuk memasukkan "algoritma pengganti untuk algoritma lain yang memiliki perilaku identik" sebagai refactor, dan saya pikir Fowler setuju. Martin Fowler sendiri mengatakan bahwa refactoring dapat membatalkan tes:
sumber
Jika tes Anda rusak saat Anda refactoring, maka Anda tidak, menurut definisi, refactoring, yang merupakan "mengubah struktur program Anda tanpa mengubah perilaku program Anda".
Terkadang Anda DO perlu mengubah perilaku tes Anda. Mungkin Anda perlu menggabungkan dua metode bersama (katakanlah, ikat () dan dengarkan () pada kelas soket TCP yang mendengarkan), jadi Anda memiliki bagian lain dari kode Anda yang mencoba dan gagal menggunakan API yang sekarang diubah. Tapi itu bukan refactoring!
sumber
Saya pikir masalah dengan pertanyaan ini, adalah bahwa orang yang berbeda mengambil kata 'refactoring' secara berbeda. Saya pikir yang terbaik adalah dengan hati-hati mendefinisikan beberapa hal yang mungkin Anda maksudkan:
Seperti yang sudah dicatat oleh satu orang lain, jika Anda menjaga API tetap sama, dan semua tes regresi Anda beroperasi pada API publik, Anda seharusnya tidak memiliki masalah. Refactoring seharusnya tidak menimbulkan masalah sama sekali. Setiap tes yang gagal BAIK berarti kode lama Anda memiliki bug dan tes Anda buruk, atau kode baru Anda memiliki bug.
Tapi itu cukup jelas. Jadi, Anda MUNGKIN bermaksud dengan refactoring, bahwa Anda mengubah API.
Jadi izinkan saya menjawab bagaimana mendekati itu!
Pertama buat API BARU, yang melakukan apa yang Anda inginkan perilaku API BARU Anda. Jika kebetulan bahwa API baru ini memiliki nama yang sama dengan API TUA, maka saya menambahkan nama _NEW ke nama API baru.
int DoSomethingInterestingAPI ();
menjadi:
OK - pada tahap ini - semua tes regresi Anda masih lulus - menggunakan nama DoSomethingInterestingAPI ().
NEXT, buka kode Anda dan ubah semua panggilan ke DoSomethingInterestingAPI () ke varian yang sesuai dari DoSomethingInterestingAPI_NEW (). Ini termasuk memperbarui / menulis ulang bagian apa pun dari tes regresi Anda yang perlu diubah untuk menggunakan API baru.
NEXT, tandai DoSomethingInterestingAPI_OLD () sebagai [[usang ()]]. Tetap gunakan API yang sudah tidak digunakan lagi selama Anda mau (sampai Anda telah memperbarui semua kode yang mungkin bergantung dengan aman).
Dengan pendekatan ini, setiap kegagalan dalam tes regresi Anda hanyalah bug dalam tes regresi itu atau mengidentifikasi bug dalam kode Anda - persis seperti yang Anda inginkan. Proses bertahap untuk merevisi API dengan secara eksplisit membuat versi _NEW dan _OLD dari API ini memungkinkan Anda untuk memiliki beberapa bit dari kode baru dan lama yang hidup berdampingan untuk sementara waktu.
sumber
Saya menganggap tes unit Anda adalah granularity yang saya sebut "bodoh" :) yaitu, mereka menguji hal-hal kecil mutlak dari setiap kelas dan fungsi. Langkah menjauh dari alat pembuat kode dan menulis tes yang berlaku untuk permukaan yang lebih besar, maka Anda dapat refactor internal sebanyak yang Anda inginkan, mengetahui bahwa antarmuka ke aplikasi Anda belum berubah, dan tes Anda masih berfungsi.
Jika Anda ingin memiliki unit test yang menguji masing-masing dan setiap metode, maka Anda harus melakukan refactor secara bersamaan.
sumber
Yang menyulitkan adalah menyambung . Setiap tes datang dengan beberapa tingkat penggabungan ke detail implementasi tetapi tes unit (terlepas dari apakah itu TDD atau tidak) sangat buruk dalam hal itu karena mengganggu internal: lebih banyak tes unit sama dengan lebih banyak kode digabungkan ke unit yaitu metode tanda tangan / antarmuka publik lainnya unit - setidaknya.
"Unit" menurut definisi adalah rincian implementasi tingkat rendah, antarmuka unit dapat dan harus diubah / dipisah / digabung dan jika tidak bermutasi saat sistem berkembang. Kelimpahan tes unit sebenarnya dapat menghambat evolusi ini lebih dari itu membantu.
Bagaimana menghindari tes yang gagal saat melakukan refactoring? Hindari sambungan. Dalam praktiknya itu berarti menghindari sebanyak mungkin unit test dan lebih memilih tes level / integrasi yang lebih tinggi daripada detail implementasi. Ingat juga bahwa tidak ada peluru perak, tes masih harus berpasangan dengan sesuatu pada tingkat tertentu, tetapi idealnya itu adalah antarmuka yang secara eksplisit diversi menggunakan Semantic Versioning yaitu biasanya di tingkat api / aplikasi yang dipublikasikan (Anda tidak ingin melakukan SemVer untuk setiap unit dalam solusi Anda).
sumber
Tes Anda terlalu erat untuk implementasi dan bukan persyaratan.
pertimbangkan menulis tes Anda dengan komentar seperti ini:
dengan cara ini Anda tidak dapat memperbaiki arti dari tes.
sumber