Basis kode yang teruji dengan baik memiliki sejumlah manfaat, tetapi pengujian aspek-aspek tertentu dari sistem menghasilkan basis kode yang tahan terhadap beberapa jenis perubahan.
Contoh sedang menguji untuk output spesifik - misalnya, teks atau HTML. Tes sering (secara naif?) Ditulis untuk mengharapkan blok teks tertentu sebagai output untuk beberapa parameter input, atau untuk mencari bagian-bagian tertentu dalam sebuah blok.
Mengubah perilaku kode, untuk memenuhi persyaratan baru atau karena pengujian kegunaan telah mengakibatkan perubahan pada antarmuka, memerlukan perubahan tes juga - mungkin bahkan tes yang tidak secara khusus tes unit untuk kode yang sedang diubah.
Bagaimana Anda mengelola pekerjaan menemukan dan menulis ulang tes ini? Bagaimana jika Anda tidak bisa begitu saja "menjalankan semuanya dan membiarkan kerangka kerja menyelesaikannya"?
Apa jenis lain dari kode-dalam-pengujian menghasilkan tes rapuh biasa?
sumber
Jawaban:
Saya tahu orang-orang TDD akan membenci jawaban ini, tetapi sebagian besar dari itu bagi saya adalah memilih dengan hati-hati di mana menguji sesuatu.
Jika saya menjadi terlalu gila dengan unit test di tingkat bawah maka tidak ada perubahan berarti yang dapat dilakukan tanpa mengubah tes unit. Jika antarmuka tidak pernah terbuka dan tidak dimaksudkan untuk digunakan kembali di luar aplikasi maka ini hanya overhead yang tidak perlu untuk apa yang mungkin merupakan perubahan cepat.
Sebaliknya jika apa yang ingin Anda ubah terkena atau digunakan kembali setiap tes yang harus Anda ubah adalah bukti dari sesuatu yang mungkin Anda langgar di tempat lain.
Dalam beberapa proyek, ini mungkin sama dengan merancang tes Anda dari tingkat penerimaan daripada dari unit pengujian. dan memiliki lebih sedikit tes unit dan lebih banyak tes gaya integrasi.
Itu tidak berarti bahwa Anda masih tidak dapat mengidentifikasi satu fitur dan kode sampai fitur itu memenuhi kriteria penerimaannya. Ini berarti bahwa dalam beberapa kasus Anda tidak akhirnya mengukur kriteria penerimaan dengan tes unit.
sumber
Saya baru saja menyelesaikan perombakan besar pada tumpukan SIP saya, menulis ulang seluruh transport TCP. (Ini adalah refactor dekat, pada skala yang agak besar, relatif terhadap kebanyakan refactoring.)
Singkatnya, ada TIdSipTcpTransport, subkelas TIdSipTransport. Semua TIdSipTransports berbagi rangkaian uji umum. Internal ke TIdSipTcpTransport adalah sejumlah kelas - peta yang berisi pasangan koneksi / inisiat-pesan, klien TCP berulir, server TCP berulir, dan sebagainya.
Inilah yang saya lakukan:
Karena itu saya tahu apa yang masih perlu saya lakukan, dalam bentuk tes komentar (*), dan tahu bahwa kode baru berfungsi seperti yang diharapkan, berkat tes baru yang saya tulis.
(*) Sungguh, Anda tidak perlu berkomentar. Hanya saja jangan jalankan mereka; 100 tes gagal tidak terlalu menggembirakan. Juga, dalam pengaturan khusus saya mengkompilasi tes lebih sedikit berarti loop tes-tulis-refactor lebih cepat.
sumber
Ketika tes rapuh, saya menemukannya biasanya karena saya menguji hal yang salah. Ambil contoh, output HTML. Jika Anda memeriksa hasil HTML yang sebenarnya, pengujian Anda akan rapuh. Tetapi Anda tidak tertarik dengan output aktual, Anda tertarik apakah itu menyampaikan informasi yang seharusnya. Sayangnya, melakukan hal itu memerlukan membuat pernyataan tentang isi otak pengguna sehingga tidak dapat dilakukan secara otomatis.
Kamu bisa:
Hal yang sama terjadi dengan SQL. Jika Anda menegaskan SQL yang sebenarnya yang berusaha dibuat oleh kelas Anda, Anda akan berada dalam masalah. Anda benar-benar ingin menegaskan hasilnya. Oleh karena itu saya menggunakan database memori SQLITE selama pengujian unit saya untuk memastikan bahwa SQL saya benar-benar melakukan apa yang seharusnya.
sumber
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:
int DoSomethingInterestingAPI_NEW (int take_more_arguments); int DoSomethingInterestingAPI_OLD (); int DoSomethingInterestingAPI () {DoSomethingInterestingAPI_NEW (whatever_default_mimics_the_old_API); 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 sedikit kode baru dan lama yang hidup berdampingan untuk sementara waktu.
Berikut adalah contoh yang baik (sulit) dari pendekatan ini dalam praktiknya. Saya memiliki fungsi BitSubstring () - di mana saya telah menggunakan pendekatan memiliki parameter ketiga menjadi COUNT bit dalam substring. Agar konsisten dengan API dan pola lain dalam C ++, saya ingin beralih untuk memulai / mengakhiri sebagai argumen untuk fungsi.
https://github.com/SophistSolutions/Stroika/commit/003dd8707405c43e735ca71116c773b108c217c0
Saya membuat fungsi BitSubstring_NEW dengan API baru, dan memperbarui semua kode saya untuk menggunakannya (meninggalkan NO LEBIH BANYAK PANGGILAN ke BitSubString). Tetapi saya meninggalkan implementasi untuk beberapa rilis (bulan) - dan menandainya sudah usang - sehingga semua orang dapat beralih ke BitSubString_NEW (dan pada saat itu mengubah argumen dari hitungan menjadi gaya awal / akhir).
LALU - ketika transisi itu selesai, saya melakukan komit lain menghapus BitSubString () dan mengganti nama BitSubString_NEW-> BitSubString () (dan mencabut nama BitSubString_NEW).
sumber