Ketika melakukan TDD dan menulis unit test, bagaimana seseorang menahan keinginan untuk "menipu" ketika menulis iterasi pertama dari kode "implementasi" yang Anda uji?
Sebagai contoh:
Mari saya perlu menghitung faktorial suatu angka. Saya mulai dengan tes unit (menggunakan MSTest) sesuatu seperti:
[TestClass]
public class CalculateFactorialTests
{
[TestMethod]
public void CalculateFactorial_5_input_returns_120()
{
// Arrange
var myMath = new MyMath();
// Act
long output = myMath.CalculateFactorial(5);
// Assert
Assert.AreEqual(120, output);
}
}
Saya menjalankan kode ini, dan gagal karena CalculateFactorial
metode ini bahkan tidak ada. Jadi, saya sekarang menulis iterasi pertama dari kode untuk mengimplementasikan metode yang diuji, menulis kode minimum yang diperlukan untuk lulus tes.
Masalahnya, saya terus tergoda untuk menulis yang berikut ini:
public class MyMath
{
public long CalculateFactorial(long input)
{
return 120;
}
}
Hal ini, secara teknis, benar bahwa itu benar-benar adalah kode minimum yang diperlukan untuk membuat tes khusus lulus (go green), meskipun jelas "menipu" karena benar-benar bahkan tidak berusaha untuk melakukan fungsi menghitung faktorial. Tentu saja, sekarang bagian refactoring menjadi latihan dalam "menulis fungsionalitas yang benar" daripada refactoring sejati implementasi. Jelas, menambahkan tes tambahan dengan parameter yang berbeda akan gagal dan memaksa refactoring, tetapi Anda harus mulai dengan satu tes itu.
Jadi, pertanyaan saya adalah, bagaimana Anda mendapatkan keseimbangan antara "menulis kode minimum untuk lulus tes" sambil tetap berfungsi dan dalam semangat apa yang sebenarnya Anda coba capai?
sumber
Jawaban:
Ini sangat sah. Merah, Hijau, Refactor.
Tes pertama berlalu.
Tambahkan tes kedua, dengan input baru.
Sekarang dengan cepat menjadi hijau, Anda bisa menambahkan if-else, yang berfungsi dengan baik. Itu berlalu, tetapi Anda belum selesai.
Bagian ketiga dari Red, Green, Refactor adalah yang paling penting. Refactor untuk menghapus duplikasi . Anda AKAN memiliki duplikasi dalam kode Anda sekarang. Dua pernyataan mengembalikan bilangan bulat. Dan satu-satunya cara untuk menghapus duplikasi itu adalah dengan mengkodekan fungsi dengan benar.
Saya tidak mengatakan jangan menuliskannya dengan benar pertama kali. Saya hanya mengatakan itu tidak curang jika Anda tidak.
sumber
Diperlukan pemahaman yang jelas tentang tujuan akhir, dan pencapaian algoritma yang memenuhi tujuan tersebut.
TDD bukan peluru ajaib untuk desain; Anda masih harus tahu cara menyelesaikan masalah menggunakan kode, dan Anda masih harus tahu cara melakukannya pada tingkat yang lebih tinggi dari beberapa baris kode untuk membuat tes lulus.
Saya suka ide TDD karena mendorong desain yang bagus; itu membuat Anda berpikir tentang bagaimana Anda dapat menulis kode Anda sehingga dapat diuji, dan secara umum filosofi itu akan mendorong kode menuju desain yang lebih baik secara keseluruhan. Tetapi Anda masih harus tahu cara merancang solusi.
Saya tidak menyukai filosofi TDD reduksionis yang mengklaim Anda dapat mengembangkan aplikasi hanya dengan menulis kode dalam jumlah terkecil untuk lulus ujian. Tanpa memikirkan arsitektur, ini tidak akan berhasil, dan contoh Anda membuktikannya.
Paman Bob Martin mengatakan ini:
sumber
Pertanyaan yang sangat bagus ... dan saya harus tidak setuju dengan hampir semua orang kecuali @Robert.
Penulisan
untuk fungsi faktorial untuk membuat satu tes lulus adalah buang - buang waktu . Itu bukan "curang", juga tidak mengikuti red-green-refactor secara harfiah. Itu salah .
Inilah alasannya:
argumen 'refactor' salah arah; jika Anda memiliki dua test case untuk 5 dan 6, kode ini masih salah, karena Anda tidak menghitung faktorial sama sekali :
jika kita mengikuti argumen 'refactor' secara harfiah , maka ketika kita memiliki 5 kasus uji, kita akan memanggil YAGNI dan mengimplementasikan fungsinya menggunakan tabel pencarian:
Tidak ada yang benar-benar menghitung apa pun, Anda . Dan itu bukan tugasnya!
sumber
Saat Anda hanya menulis satu pengujian unit, implementasi satu baris (
return 120;
) adalah sah. Menulis loop menghitung nilai 120 - itu akan curang!Tes awal yang sederhana seperti itu adalah cara yang baik untuk menangkap kasus tepi dan mencegah kesalahan satu kali. Lima sebenarnya bukan nilai input yang akan saya mulai.
Aturan praktis yang dapat berguna di sini adalah: nol, satu, banyak, banyak . Nol dan satu adalah kasus tepi penting untuk faktorial. Mereka dapat diimplementasikan dengan satu garis. Kasing uji "banyak" (mis. 5!) Kemudian akan memaksa Anda untuk menulis loop. Kasing uji "lot" (1000 !?) dapat memaksa Anda untuk mengimplementasikan algoritma alternatif untuk menangani angka yang sangat besar.
sumber
factorial(5)
adalah tes pertama yang buruk. kita mulai dari kasus yang paling sederhana dan dalam setiap iterasi kita membuat tes sedikit lebih spesifik, mendesak kode untuk menjadi sedikit lebih umum. inilah yang disebut paman bob sebagai premis prioritas transformasi ( blog.8thlight.com/uncle-bob/2013/05/27/… )Selama Anda hanya memiliki satu tes, maka kode minimal yang diperlukan untuk lulus tes benar-benar
return 120;
, dan Anda dapat dengan mudah mempertahankannya selama Anda tidak memiliki tes lagi.Ini memungkinkan Anda untuk menunda desain lebih lanjut sampai Anda benar-benar menulis tes yang menggunakan nilai pengembalian LAIN dari metode ini.
Harap diingat bahwa tes adalah versi runnable spesifikasi Anda, dan jika semua itu spesifikasi mengatakan bahwa f (6) = 120 maka yang cocok dengan tagihan sempurna.
sumber
Jika Anda dapat "menipu" sedemikian rupa, itu menunjukkan bahwa tes unit Anda cacat.
Alih-alih menguji metode faktorial dengan nilai tunggal, uji itu adalah rentang nilai. Pengujian berbasis data dapat membantu di sini.
Lihat tes unit Anda sebagai manifestasi dari persyaratan - mereka harus secara kolektif menentukan perilaku metode yang mereka uji. (Ini dikenal sebagai pengembangan yang didorong oleh perilaku - ini adalah masa depan
;-)
)Jadi tanyakan pada diri Anda - jika seseorang mengubah implementasi menjadi sesuatu yang salah, apakah tes Anda akan tetap lulus atau akankah mereka berkata "tunggu sebentar!"?
Mengingat hal itu, jika hanya tes Anda yang ada di pertanyaan Anda, maka secara teknis, implementasi yang sesuai sudah benar. Masalahnya kemudian dilihat sebagai persyaratan yang tidak didefinisikan dengan baik.
sumber
case
pernyataan tanpa akhir keswitch
, dan Anda tidak dapat menulis tes untuk setiap input dan output yang mungkin untuk contoh OP.Int64.MinValue
hinggaInt64.MaxValue
. Butuh waktu lama untuk menjalankan tetapi secara eksplisit akan menentukan persyaratan tanpa ruang untuk kesalahan. Dengan teknologi saat ini, ini tidak mungkin (saya menduga bahwa itu akan menjadi lebih umum di masa depan) dan saya setuju, Anda bisa menipu tetapi saya pikir pertanyaan OPs itu tidak praktis (tidak ada yang benar-benar akan menipu dengan cara seperti itu) dalam praktek), tetapi yang teoretis.Cukup tulis lebih banyak tes. Akhirnya, itu akan terjadi pendek untuk menulis
dari
:-)
sumber
Menulis tes "cheat" adalah OK, untuk nilai "OK" yang cukup kecil. Tetapi recall - unit testing hanya selesai ketika semua tes lulus dan tidak ada tes baru yang dapat ditulis yang akan gagal . Jika Anda benar-benar ingin memiliki metode CalculateFactorial yang berisi banyak pernyataan if (atau bahkan lebih baik, pernyataan switch / case besar :-) Anda bisa melakukan itu, dan karena Anda berurusan dengan angka presisi-tetap, kode yang diperlukan untuk mengimplementasikan ini terbatas (walaupun mungkin agak besar dan jelek, dan mungkin dibatasi oleh kompiler atau keterbatasan sistem pada ukuran maksimum kode prosedur). Pada titik ini jika Anda benar-benarbersikeras bahwa semua pengembangan harus didorong oleh tes unit Anda dapat menulis tes yang membutuhkan kode untuk menghitung hasil dalam jumlah waktu yang lebih pendek daripada yang dapat dicapai dengan mengikuti semua cabang dari pernyataan if .
Pada dasarnya, TDD dapat membantu Anda menulis kode yang mengimplementasikan persyaratan dengan benar , tetapi itu tidak dapat memaksa Anda untuk menulis kode yang baik . Terserah kamu.
Bagikan dan nikmati.
sumber
Saya 100% setuju dengan saran Robert Harveys di sini, ini bukan hanya tentang membuat tes lulus, Anda perlu mengingat tujuan keseluruhan juga.
Sebagai solusi untuk titik rasa sakit Anda dari "itu hanya diverifikasi untuk bekerja dengan set input yang diberikan" Saya akan mengusulkan menggunakan tes didorong data, seperti teori xunit. Kekuatan di balik konsep ini adalah memungkinkan Anda untuk dengan mudah membuat Spesifikasi input ke output.
Untuk faktorial, tes akan terlihat seperti ini:
Anda bahkan bisa mengimplementasikan data uji yang menyediakan (yang mengembalikan
IEnumerable<Tuple<xxx>>
) dan menyandikan invarian matematis, seperti berulang kali membagi dengan n akan menghasilkan n-1).Saya menemukan ini menjadi cara pengujian yang sangat kuat.
sumber
Jika Anda masih bisa menipu maka tes tidak cukup. Tulis lebih banyak tes! Sebagai contoh Anda, saya akan mencoba menambahkan tes dengan input 1, -1, -1000, 0, 10, 200.
Namun demikian, jika Anda benar-benar berkomitmen untuk menipu, Anda dapat menulis if-then yang tak ada habisnya. Dalam hal ini, tidak ada yang bisa membantu kecuali tinjauan kode. Anda akan segera tertangkap pada tes penerimaan ( ditulis oleh orang lain! )
Masalah dengan tes unit kadang-kadang programmer melihatnya sebagai pekerjaan yang tidak perlu. Cara yang benar untuk melihatnya adalah sebagai alat bagi Anda untuk membuat hasil pekerjaan Anda benar. Jadi jika Anda membuat if-then, Anda tahu secara tidak sadar bahwa ada kasus lain yang perlu dipertimbangkan. Ini berarti Anda harus menulis tes lain. Dan seterusnya dan seterusnya sampai Anda menyadari bahwa kecurangan tidak berfungsi dan lebih baik hanya kode cara yang benar. Jika Anda masih merasa bahwa Anda belum selesai, Anda belum selesai.
sumber
Saya akan menyarankan bahwa pilihan tes Anda bukan tes terbaik.
Saya akan memulai dengan:
faktorial (1) sebagai tes pertama,
faktorial (0) sebagai yang kedua
faktorial (-ve) sebagai yang ketiga
dan kemudian melanjutkan dengan kasus-kasus non-sepele
dan selesaikan dengan case overflow.
sumber
-ve
??