Bagaimana menghadapi ujian kelulusan dari awal di TDD

8

Saya mencoba untuk berlatih TDD dalam proyek pribadi saya dan saya ingin tahu bagaimana menghadapi situasi ketika setelah menambahkan tes baru lulus dari awal berdasarkan implementasi yang ada?

Di satu sisi baru dapat menguji memberikan dokumentasi tambahan dari desain dan perlindungan dari pelanggaran asumsi.

Di sisi lain jika tes lulus tanpa perubahan kode maka itu "mencurigakan" apakah benar-benar menguji apa yang seharusnya.

Pada dasarnya apa yang dapat dilakukan untuk mengkonfirmasi kebenaran tes yang menegaskan perilaku yang sudah diterapkan?

AGRzes
sumber
5
pendekatan favorit saya adalah pengujian mutasi, saya mengubah kode yang diuji dan memeriksa apakah tes mulai gagal
nyamuk
2
@gnat - Saya percaya ada beberapa tumpang tindih tapi saya tidak merasa itu adalah pertanyaan yang sama. Di sini saya bertanya secara spesifik tentang TDD dan saya percaya bahwa "Jika Anda menemukan diri Anda dalam situasi seperti itu maka Anda melakukan TDD yang salah karena ..." akan menjadi jawaban yang valid.
AGrzes
5
itu tidak masalah karena Anda belum melakukan TDD ketika Anda menulis tes terhadap implementasi yang sudah ada (dan bekerja dengan benar)
nyamuk
2
"Dan apa yang kamu lakukan ketika kamu menemukan itu?" Saya menjalankan tes baru (hanya ini) dengan alat cakupan dan memeriksa apakah jalur yang diharapkan dijalankan. Meluncur kembali hanya merupakan pilihan jika Anda melakukan proyek untuk pelatihan. BTW: Hari Global Coderetreat adalah peluang besar untuk berlatih TDD ...
Timothy Truckle
2
@Secretoff'sSecret pribadi, saya tidak percaya tes apa pun yang saya lihat gagal.
RubberDuck

Jawaban:

13

Lulus tes dari saat Anda menulisnya bisa menjadi masalah dengan proses TDD Anda, tetapi itu tidak berarti itu salah dengan sendirinya.

Tes Anda mungkin dilewati secara kebetulan.

Katakanlah Anda memiliki metode yang mengembalikan biaya penarikan uang menggunakan ATM. Anda sekarang diminta untuk menambah aturan baru bahwa jika pemilik kartu berusia lebih dari 60 tahun, biayanya adalah 0. Jadi kami mengujinya, berharap gagal:

assertTrue(ATM.withdrawalCost(clientOver60) == 0)

Anda mungkin berharap ini gagal. Tapi itu berlalu, karena klien kebetulan adalah klien VIP, yang memiliki penarikan gratis. Sekarang Anda bisa kembali ke metode withdrawalCost dan memodifikasinya untuk membuatnya gagal, tetapi itu tidak terlalu masuk akal. Tulis tes baru untuk menunjukkan kode Anda salah:

assertTrue(ATM.withdrawalCost(nonVIPclientOver60) == 0)

Sekarang gagal, Anda pergi dan kode sampai lulus, lalu ulangi sampai selesai.

Haruskah Anda menghapus tes itu, karena tidak ada bedanya? Tidak! Itu menggambarkan fungsionalitas yang diharapkan dari metode penarikan ATM. Jika Anda menghapusnya dan suatu hari biaya penarikan biaya 0 untuk klien VIP berubah, pernyataan pertama harus tetap benar.

Setelah mengatakan ini, untuk TDD yang tepat Anda tidak harus mengkode hal-hal sebelum tes Anda, dan kemudian menguji hal-hal yang Anda tahu Anda akan lulus. Saya tidak menganggap ini kasus yang Anda tanyakan.

Saya percaya siklus gagal-kode-lulus dimaksudkan untuk menghindari "Saya akan menulis 3 tes ini yang akan gagal dan 2 ini akan berlalu karena saya sudah mengkodekannya mengetahui apa tes yang akan menjadi" pengembangan didorong. Anda harus tahu beberapa orang mungkin merasa sebaliknya. Dengar alasan mereka, mereka mungkin valid juga.

Maximiliano
sumber
10

Tes yang lulus sejak awal biasanya akan terjadi ketika seseorang mengimplementasikan sesuatu dengan cara yang lebih umum daripada yang sebenarnya diperlukan untuk tes yang ada. Ini cukup normal : unit test hanya dapat memberikan sejumlah kecil nilai input terbatas untuk fungsi yang diberikan, tetapi sebagian besar fungsi ditulis untuk sejumlah besar nilai input yang mungkin. Seringkali implementasi yang dirancang khusus untuk kasus uji saat ini akan lebih rumit daripada solusi yang lebih umum. Jika itu masalahnya, maka akan menjadi rumit dan rawan kesalahan untuk secara artifisial merancang kode dengan cara hanya bekerja untuk kasus uji dan gagal untuk yang lainnya.

Misalnya, katakanlah Anda memerlukan fungsi untuk mengembalikan minimum beberapa nilai dari array yang diberikan. Anda membuat implementasi, didorong oleh tes dengan larik yang hanya berisi satu atau dua nilai. Tetapi alih-alih mengimplementasikan ini dengan cara yang berbelit-belit dengan melakukan perbandingan pada elemen yang berbeda (mungkin hanya dua elemen pertama), Anda memanggil fungsi array minimum dari pustaka standar ekosistem bahasa Anda dan menjadikan implementasinya satu garis . Saat Anda sekarang memutuskan untuk menambahkan tes dengan array lima elemen, tes mungkin akan lulus sejak awal.

Tapi bagaimana Anda tahu kalau tesnya tidak "hijau" karena ada bug dalam tes itu sendiri? Cara sederhana dan mudah untuk mendekati ini adalah dengan membuat modifikasi sementara pada subjek yang diuji untuk membuat tes gagal. Misalnya, Anda dapat dengan sengaja menambahkan garis if (array.size()==5) return 123ke fungsi Anda. Sekarang tes lima elemen Anda akan gagal, jadi Anda tahu

  • tes dieksekusi
  • panggilan Assert dalam tes dijalankan
  • panggilan tegas dalam tes memvalidasi hal yang benar

yang seharusnya memberi Anda kepercayaan diri dalam ujian. Setelah Anda melihat tes gagal, batalkan modifikasi, dan tes harus lulus lagi.

Atau, Anda dapat mengubah hasil tes yang diharapkan: katakanlah tes kelulusan Anda berisi pernyataan seperti

 int result = Subject.UnderTest(...);
 Assert.AreEqual(1,result);

maka Anda dapat mengedit tes dan mengganti "1" dengan "2". Ketika tes gagal (seperti yang diharapkan), Anda tahu itu berfungsi sebagaimana mestinya, dan Anda dapat membatalkan penggantian dan melihat apakah tes sekarang menjadi hijau. Risiko memasukkan bug ke dalam pengujian dengan penggantian semacam itu sangat kecil, jadi ini mungkin dapat diterima untuk sebagian besar kasus dunia nyata.

Cara lain yang mungkin bisa diperdebatkan adalah dengan menetapkan breakpoint ke dalam tes dan menggunakan debugger untuk melewatinya. Itu juga harus memberi Anda rasa percaya diri bahwa kode pengujian benar-benar dijalankan, dan memberi Anda kemungkinan untuk memvalidasi jalur melalui pengujian dengan inspeksi langkah demi langkah. Namun, seseorang harus sangat berhati-hati untuk tidak mengabaikan kesalahan dalam jalur kode khusus untuk tes gagal. Untuk pengujian yang rumit, Anda dapat mempertimbangkan untuk melakukan keduanya - membuatnya gagal secara buatan dan menggunakan debugger untuk memeriksanya.

Doc Brown
sumber