Apa tindakan terbaik dalam TDD jika, setelah menerapkan logika dengan benar, tes masih gagal (karena ada kesalahan dalam tes)?
Misalnya, Anda ingin mengembangkan fungsi berikut:
int add(int a, int b) {
return a + b;
}
Misalkan kita mengembangkannya dalam langkah-langkah berikut:
Tes tulis (belum berfungsi):
// test1 Assert.assertEquals(5, add(2, 3));
Menghasilkan kesalahan kompilasi.
Menulis implementasi fungsi dummy:
int add(int a, int b) { return 5; }
Hasil:
test1
melewati.Tambahkan test case lain:
// test2 -- notice the wrong expected value (should be 11)! Assert.assertEquals(12, add(5, 6));
Hasil:
test2
gagal,test1
masih lewat.Tulis implementasi nyata:
int add(int a, int b) { return a + b; }
Hasil:
test1
masih lewat,test2
masih gagal (sejak11 != 12
).
Dalam kasus khusus ini: apakah akan lebih baik untuk:
- memperbaiki
test2
, dan melihat bahwa itu sekarang lewat, atau - hapus bagian implementasi yang baru (yaitu kembali ke langkah # 2 di atas), perbaiki
test2
dan biarkan gagal, dan kemudian masukkan kembali implementasi yang benar (langkah # 4. di atas).
Atau ada cara lain yang lebih pintar?
Sementara saya mengerti bahwa contoh masalah agak sepele, saya tertarik pada apa yang harus dilakukan dalam kasus generik, yang mungkin lebih kompleks daripada penambahan dua angka.
EDIT (Menanggapi jawaban dari @Thomas Junk):
Fokus pertanyaan ini adalah apa yang disarankan TDD dalam kasus seperti itu, bukan apa yang "praktik terbaik universal" untuk mencapai kode atau tes yang baik (yang mungkin berbeda dari cara TDD).
Jawaban:
Yang benar-benar penting adalah bahwa Anda melihat tes lulus dan gagal.
Apakah Anda menghapus kode untuk membuat tes gagal maka menulis ulang kode atau menyelinap ke clipboard hanya untuk menempelkannya kembali nanti tidak masalah. TDD tidak pernah mengatakan Anda harus mengetik ulang apa pun. Ia ingin mengetahui tes lulus hanya ketika harus lulus dan gagal hanya ketika seharusnya gagal.
Melihat tes lulus dan gagal adalah cara Anda menguji tes. Jangan pernah percaya pada tes yang belum pernah Anda lihat lakukan keduanya.
Refactoring Against The Red Bar memberi kita langkah formal untuk refactoring tes kerja:
Namun, kami tidak refactoring tes kerja. Kita harus mengubah tes kereta. Satu masalah adalah kode yang diperkenalkan sementara hanya tes ini yang membahasnya. Kode tersebut harus diputar kembali dan diperkenalkan kembali setelah tes diperbaiki.
Jika bukan itu masalahnya, dan cakupan kode bukan masalah karena tes lain yang mencakup kode, Anda dapat mengubah tes dan memperkenalkannya sebagai tes hijau.
Di sini, kode juga sedang diputar kembali tetapi cukup untuk menyebabkan tes gagal. Jika itu tidak cukup untuk mencakup semua kode yang diperkenalkan sementara hanya tercakup oleh tes kereta kami perlu kode yang lebih besar memutar kembali dan lebih banyak tes.
Memperkenalkan tes hijau
Memecah kode dapat mengomentari kode atau memindahkannya ke tempat lain hanya untuk menempelkannya kembali nanti. Ini menunjukkan kepada kita lingkup kode yang dicakup tes.
Untuk dua putaran terakhir ini, Anda kembali ke siklus hijau merah normal. Anda hanya menempel bukannya mengetik untuk membatalkan kode dan membuat lulus ujian. Jadi pastikan Anda hanya menyisipkan cukup untuk lulus ujian.
Pola keseluruhan di sini adalah untuk melihat warna tes mengubah cara yang kita harapkan. Perhatikan bahwa ini menciptakan situasi di mana Anda secara singkat memiliki tes hijau yang tidak dipercaya. Berhati-hatilah agar tidak terganggu dan lupa di mana Anda berada dalam langkah-langkah ini.
Terima kasih saya kepada RubberDuck untuk tautan Embracing the Red Bar .
sumber
Apa tujuan keseluruhan yang ingin Anda capai?
Membuat tes yang bagus?
Membuat implementasi yang benar ?
Melakukan TTD dengan benar secara agama ?
Bukan dari salah satu di atas?
Mungkin Anda terlalu memikirkan hubungan Anda dengan tes dan pengujian.
Tes tidak memberikan jaminan tentang kebenaran implementasi. Setelah semua tes lulus tidak mengatakan apa-apa, apakah perangkat lunak Anda melakukan apa yang seharusnya; itu tidak membuat pernyataan penting tentang perangkat lunak Anda.
Ambil contoh Anda:
Implementasi "benar" penambahan akan menjadi kode yang setara dengan
a+b
. Dan selama kode Anda melakukan itu, Anda akan mengatakan algoritma itu benar dalam apa yang dilakukannya dan diimplementasikan dengan benar .Pada pandangan pertama , kami berdua akan setuju, bahwa ini adalah implementasi dari penambahan.
Tapi apa yang kita lakukan benar-benar tidak mengatakan, bahwa kode ini adalah pelaksanaan
addition
itu hanya berperilaku untuk tingkat tertentu seperti: memikirkan bilangan bulat melimpah .Integer overflow memang terjadi dalam kode, tetapi tidak dalam konsep
addition
. Jadi: kode Anda berlaku sampai batas tertentu seperti konsepaddition
, tetapi tidakaddition
.Sudut pandang yang agak filosofis ini memiliki beberapa konsekuensi.
Dan satu, yang bisa Anda katakan, tes tidak lebih dari asumsi perilaku yang diharapkan dari kode Anda. Dalam pengujian kode Anda, Anda bisa (mungkin) tidak pernah pastikan, Anda pelaksanaannya adalah benar , yang terbaik Anda bisa katakan adalah, bahwa harapan Anda pada apa hasil kode Anda delivers berada atau tidak dipenuhi; baik itu, bahwa kode Anda salah, baik itu, bahwa tes Anda salah atau baik itu, bahwa keduanya salah.
Tes yang berguna membantu Anda untuk memperbaiki harapan Anda pada apa yang harus dilakukan kode: selama saya tidak mengubah harapan saya dan selama kode yang dimodifikasi memberi saya hasil yang saya harapkan, saya bisa yakin, bahwa asumsi yang saya buat tentang hasilnya sepertinya berhasil.
Itu tidak membantu, ketika Anda membuat asumsi yang salah; tapi hey! setidaknya itu mencegah skizofrenia: mengharapkan hasil yang berbeda ketika seharusnya tidak ada.
tl; dr
Tes Anda adalah asumsi tentang perilaku kode. Jika Anda memiliki alasan kuat untuk menganggap implementasi Anda benar, perbaiki tes dan lihat apakah asumsi itu berlaku.
sumber
datatype
itu jelas merupakan pilihan yang salah. Sebuah tes akan mengungkapkan bahwa: harapan Anda akan »bekerja untuk angka besar« dan dalam beberapa kasus tidak terpenuhi. Maka pertanyaannya adalah bagaimana menangani kasus-kasus itu. Apakah mereka kasus sudut? Ketika ya, bagaimana cara menghadapinya? Mungkin beberapa klausa quard membantu mencegah kekacauan yang lebih besar. Jawabannya adalah konteks terikat.Anda perlu tahu bahwa tes ini akan gagal jika implementasi salah, yang tidak sama dengan lulus jika pelaksanaannya benar. Oleh karena itu Anda harus meletakkan kode kembali ke kondisi di mana Anda mengharapkannya gagal sebelum memperbaiki tes, dan pastikan gagal karena alasan yang Anda harapkan (yaitu
5 != 12
), daripada sesuatu yang tidak Anda prediksi.sumber
assertTrue(5 == add(2, 3))
memberikan hasil yang kurang berguna daripadaassertEqual(5, add(2, 3))
meskipun keduanya menguji hal yang sama).Dalam kasus khusus ini, jika Anda mengubah 12 ke 11, dan tes sekarang berlalu, saya pikir Anda telah melakukan pekerjaan yang baik untuk menguji tes serta implementasinya, jadi tidak banyak yang perlu melalui rintangan tambahan.
Namun, masalah yang sama dapat muncul dalam situasi yang lebih kompleks, seperti ketika Anda memiliki kesalahan dalam kode pengaturan Anda. Dalam hal ini, setelah memperbaiki tes Anda, Anda mungkin harus mencoba memutasikan implementasi Anda sedemikian rupa agar tes tertentu gagal, dan kemudian mengembalikan mutasi. Jika mengembalikan implementasi adalah cara termudah untuk melakukannya, maka itu tidak masalah. Dalam contoh Anda, Anda mungkin bermutasi
a + b
kea + a
ataua * b
.Atau, jika Anda dapat sedikit mengubah pernyataan dan melihat tes gagal, itu bisa sangat efektif untuk menguji tes.
sumber
Saya akan mengatakan, ini adalah kasus untuk sistem kontrol versi favorit Anda:
Tahap koreksi tes, menjaga perubahan kode Anda di direktori kerja Anda.
Komit dengan pesan yang sesuai
Fixed test ... to expect correct output
.Dengan
git
, ini mungkin memerlukan penggunaangit add -p
jika pengujian dan implementasi berada dalam file yang sama, jika tidak, Anda dapat dengan mudah mem-stage kedua file secara terpisah.Komit kode implementasi.
Kembali ke masa lalu untuk menguji komit yang dilakukan pada langkah 1, memastikan bahwa tes benar-benar gagal .
Anda lihat, dengan begitu Anda tidak bergantung pada kecakapan mengedit Anda untuk memindahkan kode implementasi Anda keluar dari jalan saat Anda menguji tes gagal Anda. Anda menggunakan VCS Anda untuk menyimpan pekerjaan Anda dan untuk memastikan bahwa riwayat VCS yang tercatat dengan benar mencakup tes gagal dan lulus.
sumber