Katakanlah Anda sedang menulis gaya TDD game Yahtzee. Anda ingin menguji bagian dari kode yang menentukan apakah satu set lima gulungan mati adalah rumah penuh. Sejauh yang saya tahu, ketika melakukan TDD, Anda mengikuti prinsip-prinsip ini:
- Tulis tes terlebih dahulu
- Tulis hal paling sederhana yang berhasil
- Memperbaiki dan memperbaiki
Jadi tes awal mungkin terlihat seperti ini:
public void Returns_true_when_roll_is_full_house()
{
FullHouseTester sut = new FullHouseTester();
var actual = sut.IsFullHouse(1, 1, 1, 2, 2);
Assert.IsTrue(actual);
}
Saat mengikuti "Tulis hal paling sederhana yang berhasil", Anda sekarang harus menulis IsFullHouse
metode seperti ini:
public bool IsFullHouse(int roll1, int roll2, int roll3, int roll4, int roll5)
{
if (roll1 == 1 && roll2 == 1 && roll3 == 1 && roll4 == 2 && roll5 == 2)
{
return true;
}
return false;
}
Ini menghasilkan tes hijau tetapi implementasinya tidak lengkap.
Haruskah Anda menguji setiap kemungkinan kombinasi yang valid (baik nilai dan posisi) untuk rumah lengkap? Itu sepertinya satu-satunya cara untuk benar-benar yakin bahwa IsFullHouse
kode Anda benar-benar diuji dan benar, tetapi juga terdengar sangat gila untuk melakukan itu.
Bagaimana Anda menguji sesuatu seperti ini?
Memperbarui
Erik dan Kilian menunjukkan bahwa menggunakan literal dalam implementasi awal untuk mendapatkan tes hijau mungkin bukan ide terbaik. Saya ingin menjelaskan mengapa saya melakukan itu dan penjelasan itu tidak sesuai dengan komentar.
Pengalaman praktis saya dengan pengujian unit (terutama menggunakan pendekatan TDD) sangat terbatas. Saya ingat menonton rekaman Masterclass TDD Roy Osherove di Tekpub. Dalam salah satu episode ia membangun gaya Kalkulator String TDD. Spesifikasi lengkap dari String Calculator dapat ditemukan di sini: http://osherove.com/tdd-kata-1/
Dia mulai dengan tes seperti ini:
public void Add_with_empty_string_should_return_zero()
{
StringCalculator sut = new StringCalculator();
int result = sut.Add("");
Assert.AreEqual(0, result);
}
Ini menghasilkan implementasi Add
metode ini yang pertama :
public int Add(string input)
{
return 0;
}
Kemudian tes ini ditambahkan:
public void Add_with_one_number_string_should_return_number()
{
StringCalculator sut = new StringCalculator();
int result = sut.Add("1");
Assert.AreEqual(1, result);
}
Dan Add
metodenya adalah refactored:
public int Add(string input)
{
if (input.Length == 0)
{
return 0;
}
return 1;
}
Setelah setiap langkah Roy mengatakan "Tulis hal paling sederhana yang akan berhasil".
Jadi saya pikir saya akan mencoba pendekatan ini ketika mencoba untuk melakukan permainan Yahtzee gaya TDD.
sumber
if (roll1 == 1 && roll2 == 1 && roll3 == 1 && roll4 == 2 && roll5 == 2)
Jawaban:
Sudah ada banyak jawaban bagus untuk pertanyaan ini, dan saya sudah mengomentari dan membatalkan beberapa di antaranya. Tetap saja, saya ingin menambahkan beberapa pemikiran.
Fleksibilitas bukan untuk pemula
OP dengan jelas menyatakan bahwa dia tidak berpengalaman dengan TDD, dan saya pikir jawaban yang baik harus memperhitungkannya. Dalam terminologi model Dreyfus tentang perolehan keterampilan , dia mungkin seorang pemula . Tidak ada yang salah dengan menjadi novis - kita semua adalah novis ketika kita mulai mempelajari sesuatu yang baru. Namun, apa yang dijelaskan oleh model Dreyfus adalah bahwa para pemula ditandai oleh
Itu bukan deskripsi tentang kekurangan kepribadian, jadi tidak ada alasan untuk malu akan hal itu - ini adalah tahap yang harus kita semua lalui untuk mempelajari sesuatu yang baru.
Ini juga berlaku untuk TDD.
Sementara saya setuju dengan banyak jawaban lain di sini bahwa TDD tidak harus dogmatis, dan bahwa kadang-kadang bisa lebih bermanfaat untuk bekerja dengan cara alternatif, itu tidak membantu siapa pun yang baru memulai. Bagaimana Anda bisa melakukan penilaian bebas ketika Anda tidak memiliki pengalaman?
Jika seorang pemula menerima saran bahwa kadang-kadang tidak apa-apa untuk tidak melakukan TDD, bagaimana ia bisa menentukan kapan tidak apa-apa untuk tidak melakukan TDD?
Tanpa pengalaman atau bimbingan, satu-satunya hal yang dapat dilakukan seorang pemula adalah keluar dari TDD setiap kali menjadi terlalu sulit. Itu sifat manusia, tetapi bukan cara yang baik untuk belajar.
Dengarkan tesnya
Melewatkan TDD setiap saat menjadi sulit adalah kehilangan salah satu manfaat terpenting dari TDD. Pengujian memberikan umpan balik awal tentang API SUT. Jika tes ini sulit untuk ditulis, itu pertanda penting bahwa SUT sulit digunakan.
Inilah alasan mengapa salah satu pesan paling penting dari GOOS adalah: dengarkan tes Anda!
Dalam kasus pertanyaan ini, reaksi pertama saya ketika melihat API yang diusulkan dari game Yahtzee, dan diskusi tentang kombinatorik yang dapat ditemukan di halaman ini, adalah bahwa ini adalah umpan balik penting tentang API.
Apakah API harus mewakili gulungan dadu sebagai urutan bilangan bulat yang dipesan? Bagi saya, itu bau Obsesi Primitif . Itu sebabnya saya senang melihat jawaban dari tallseth yang menyarankan pengenalan
Roll
kelas. Saya pikir itu saran yang bagus.Namun, saya berpikir bahwa beberapa komentar untuk jawaban itu salah. Apa yang kemudian disarankan oleh TDD adalah bahwa begitu Anda mendapatkan ide bahwa suatu
Roll
kelas akan menjadi ide yang baik, Anda menangguhkan pekerjaan pada SUT asli dan mulai mengerjakan TDD diRoll
kelas.Meskipun saya setuju bahwa TDD lebih ditujukan pada 'happy path' daripada pengujian komprehensif, namun tetap membantu memecah sistem menjadi beberapa unit yang dapat dikelola. Sebuah
Roll
suara kelas seperti sesuatu yang Anda bisa TDD selesai jauh lebih mudah.Kemudian, begitu
Roll
kelas telah cukup berkembang, apakah Anda akan kembali ke SUT asli dan menyempurnakannya dalam halRoll
input.Saran dari Test Helper tidak selalu menyiratkan keacakan - itu hanya cara untuk membuat tes lebih mudah dibaca.
Cara lain untuk mendekati dan memodelkan input dalam hal
Roll
instance adalah dengan memperkenalkan Test Data Builder .Merah / Hijau / Refactor adalah proses tiga tahap
Sementara saya setuju dengan sentimen umum bahwa (jika Anda cukup berpengalaman dalam TDD), Anda tidak perlu menggunakan TDD dengan ketat, saya pikir itu saran yang sangat buruk dalam kasus latihan Yahtzee. Meskipun saya tidak tahu detail dari peraturan Yahtzee, saya tidak melihat argumen yang meyakinkan di sini bahwa Anda tidak dapat bertahan dengan ketat dengan proses Merah / Hijau / Refactor dan masih sampai pada hasil yang tepat.
Apa yang kebanyakan orang di sini sepertinya lupa adalah tahap ketiga dari proses Merah / Hijau / Refactor. Pertama, Anda menulis tes. Kemudian Anda menulis implementasi paling sederhana yang melewati semua tes. Maka Anda refactor.
Di sini, di negara bagian ketiga ini, Anda dapat membawa semua keterampilan profesional Anda. Di sinilah Anda diizinkan untuk merenungkan kode.
Namun, saya pikir ini adalah alasan untuk menyatakan bahwa Anda seharusnya hanya "Menulis hal yang paling sederhana yang tidak sepenuhnya braindead dan jelas salah yang berhasil". Jika Anda (berpikir Anda) cukup tahu tentang implementasi sebelumnya, maka semua kekurangan solusi lengkap akan menjadi jelas salah . Sejauh saran berjalan, maka, ini cukup berguna bagi seorang pemula.
Apa yang benar-benar harus terjadi adalah bahwa jika Anda dapat membuat semua tes lulus dengan implementasi yang jelas salah , itu umpan balik yang harus Anda tulis tes lain .
Mengejutkan betapa seringnya melakukan itu menuntun Anda ke arah implementasi yang sama sekali berbeda dari yang Anda pikirkan pertama kali. Terkadang, alternatif yang tumbuh seperti itu ternyata lebih baik dari rencana semula.
Kekakuan adalah alat belajar
Sangat masuk akal untuk tetap dengan proses yang ketat seperti Merah / Hijau / Refactor selama kita masih belajar. Ini memaksa pelajar untuk mendapatkan pengalaman dengan TDD tidak hanya saat itu mudah, tetapi juga saat itu sulit.
Hanya ketika Anda telah menguasai semua bagian yang sulit Anda berada dalam posisi untuk membuat keputusan berdasarkan informasi tentang kapan harus menyimpang dari jalan 'benar'. Saat itulah Anda mulai membentuk jalur Anda sendiri.
sumber
Sebagai penafian, ini adalah TDD ketika saya mempraktikkannya dan, sebagaimana ditunjukkan Kilian dengan tepat, saya akan mewaspadai siapa pun yang menyarankan bahwa ada satu cara yang tepat untuk mempraktikkannya. Tapi mungkin itu akan membantu Anda ...
Pertama-tama, hal paling sederhana yang dapat Anda lakukan untuk lulus ujian adalah:
Ini penting karena bukan karena beberapa praktik TDD, tetapi karena hard-cording dalam semua literal itu sebenarnya bukan ide yang baik. Salah satu hal paling sulit untuk membungkus kepala Anda dengan TDD adalah bahwa itu bukan strategi pengujian yang komprehensif - itu adalah cara untuk menjaga terhadap regresi dan menandai kemajuan sambil menjaga kode tetap sederhana. Ini adalah strategi pengembangan dan bukan strategi pengujian.
Alasan saya menyebutkan perbedaan ini adalah membantu memandu tes apa yang harus Anda tulis. Jawaban untuk "tes apa yang harus saya tulis?" adalah "tes apa pun yang Anda butuhkan untuk mendapatkan kode seperti yang Anda inginkan." Pikirkan TDD sebagai cara untuk membantu Anda mencari algoritma dan alasan tentang kode Anda. Jadi, mengingat tes Anda dan penerapan "hijau sederhana" saya, tes apa yang akan terjadi selanjutnya? Nah, Anda telah menetapkan sesuatu yang merupakan rumah penuh, jadi kapan bukan rumah penuh?
Sekarang Anda harus mencari cara untuk membedakan antara dua kasus uji yang bermakna . Saya pribadi akan menempelkan sedikit informasi klarifikasi ke "lakukan hal paling sederhana untuk membuat lulus ujian" dan mengatakan "lakukan hal paling sederhana untuk membuat lulus ujian yang semakin jauh implementasi Anda." Menulis tes yang gagal adalah alasan Anda untuk mengubah kode, jadi ketika Anda menulis setiap tes, Anda harus bertanya pada diri sendiri "apa yang tidak saya lakukan dengan kode yang saya inginkan dan bagaimana saya bisa mengekspos kekurangan itu?" Ini juga dapat membantu Anda membuat kode Anda kuat dan menangani kasus tepi. Apa yang Anda lakukan jika seorang pemanggil memasukkan omong kosong?
Singkatnya, jika Anda menguji setiap kombinasi nilai, Anda hampir pasti salah melakukannya (dan kemungkinan akan berakhir dengan ledakan kombinasi conditional). Ketika datang ke TDD, Anda harus menulis jumlah minimum kasus uji yang diperlukan untuk mendapatkan algoritma yang Anda inginkan. Setiap tes lebih lanjut yang Anda tulis akan mulai hijau dan dengan demikian menjadi dokumentasi, pada dasarnya, dan bukan bagian dari proses TDD. Anda hanya akan menulis kasus uji TDD lebih lanjut jika persyaratan berubah atau bug terbuka, dalam hal ini Anda akan mendokumentasikan kekurangan dengan tes dan kemudian membuatnya lulus.
Memperbarui:
Saya memulai ini sebagai komentar untuk menanggapi pembaruan Anda, tetapi mulai cukup lama ...
Saya akan mengatakan bahwa masalahnya bukan dengan keberadaan literal, titik, tetapi dengan hal 'paling sederhana' menjadi syarat 5-bagian. Ketika Anda memikirkannya, syarat 5-bagian sebenarnya cukup rumit. Adalah umum untuk menggunakan literal selama langkah merah ke hijau dan kemudian mengabstraksikannya menjadi konstanta pada langkah refactor atau menggeneralisasikannya dalam pengujian selanjutnya.
Selama perjalanan saya sendiri dengan TDD, saya menyadari bahwa ada perbedaan penting yang harus dibuat - itu tidak baik untuk membingungkan "sederhana" dan "tumpul". Yaitu, ketika saya mulai, saya melihat orang-orang melakukan TDD dan saya pikir "mereka hanya melakukan hal yang paling bodoh untuk membuat tes lulus" dan saya menirukannya sebentar, sampai saya menyadari bahwa "sederhana" agak berbeda. dari "tumpul". Terkadang mereka tumpang tindih, tetapi seringkali tidak.
Jadi, permintaan maaf jika saya memberi kesan bahwa keberadaan literal adalah masalahnya - tidak. Saya akan mengatakan kompleksitas persyaratan dengan 5 klausa adalah masalahnya. Merah-ke-hijau pertama Anda bisa saja "mengembalikan benar" karena itu benar-benar sederhana (dan tumpul, secara kebetulan). Kasing uji berikutnya, dengan (1, 2, 3, 4, 5) harus mengembalikan false, dan di sinilah Anda mulai meninggalkan "tumpul". Anda harus bertanya pada diri sendiri "mengapa (1, 1, 1, 2, 2) rumah penuh dan (1, 2, 3, 4, 5) tidak?" Hal paling sederhana yang Anda dapat lakukan adalah bahwa seseorang memiliki elemen urutan terakhir 5 atau elemen urutan kedua 2 dan yang lainnya tidak. Itu sederhana, tetapi mereka juga tumpul (tidak perlu). Apa yang Anda benar-benar ingin kendarai adalah "berapa jumlah yang sama yang mereka miliki?" Jadi, Anda mungkin mendapatkan tes kedua untuk lulus dengan memeriksa untuk melihat apakah ada pengulangan atau tidak. Dalam satu dengan pengulangan, Anda memiliki rumah penuh, dan yang lain tidak. Sekarang tes berlalu, dan Anda menulis test case lain yang memiliki pengulangan tetapi bukan rumah penuh untuk lebih menyempurnakan algoritma Anda.
Anda mungkin atau mungkin tidak melakukan ini dengan literal saat Anda pergi, dan itu baik-baik saja jika Anda melakukannya. Tetapi ide umum adalah mengembangkan algoritma Anda 'secara organik' saat Anda menambahkan lebih banyak kasus.
sumber
Menguji lima nilai literal tertentu dalam kombinasi tertentu tidaklah "sederhana" bagi otak saya yang demam. Jika solusi untuk suatu masalah benar-benar jelas (hitung apakah Anda memiliki tepat tiga dan tepat dua dari nilai apa pun ), maka tentu saja silakan dan kodekan solusi itu, dan tulis beberapa tes yang akan sangat, sangat tidak mungkin memuaskan secara tidak sengaja dengan jumlah kode yang Anda tulis (yaitu literal yang berbeda dan urutan yang berbeda dari tiga kali lipat dan ganda).
Pepatah TDD benar-benar alat, bukan keyakinan agama. Maksud mereka adalah membuat Anda menulis kode yang benar dan diperhitungkan dengan baik dengan cepat. Jika pepatah jelas menghalangi hal itu, langsung saja maju ke langkah berikutnya. Akan ada banyak bit yang tidak jelas dalam proyek Anda di mana Anda dapat menerapkannya.
sumber
Jawaban Erik luar biasa, tetapi saya pikir saya mungkin akan berbagi trik dalam penulisan ujian.
Mulai dengan tes ini:
Tes ini menjadi lebih baik jika Anda membuat
Roll
kelas daripada melewati 5 params:Itu memberikan implementasi ini:
Kemudian tulis tes ini:
Setelah itu berlalu, tulis yang ini:
Setelah itu, saya yakin Anda tidak perlu menulis lagi (mungkin dua pasang, atau mungkin yahtzee, jika Anda pikir itu bukan rumah penuh).
Jelas, terapkan metode Apa pun Anda untuk mengembalikan Rolls acak yang memenuhi kriteria Anda.
Ada beberapa manfaat dari pendekatan ini:
sumber
IsFullHouse
benar-benar kembalitrue
kalaupairNum == trioNum
?Saya dapat memikirkan dua cara utama yang akan saya pertimbangkan saat menguji ini;
Tambahkan "beberapa" lebih banyak test case (~ 5) set full-house yang valid, dan jumlah yang sama dari kesalahan yang diharapkan ({1, 1, 2, 3, 3} adalah yang baik. Ingatlah bahwa 5 yang misalnya dapat berupa diakui sebagai "3 dengan jumlah yang sama plus satu pasang" dengan implementasi yang salah). Metode ini mengasumsikan pengembang tidak hanya mencoba untuk lulus tes, tetapi sebenarnya menerapkannya dengan benar.
Uji semua set dadu yang mungkin (hanya ada 252 yang berbeda). Ini tentu saja mengasumsikan Anda memiliki beberapa cara untuk mengetahui apa jawaban yang diharapkan (dalam pengujian ini dikenal sebagai
oracle
.) Ini bisa menjadi implementasi referensi dari fungsi yang sama, atau manusia. Jika Anda ingin benar-benar teliti, mungkin layak untuk mengkode setiap hasil yang diharapkan secara manual.Kebetulan, saya pernah menulis AI Yahtzee sekali, yang tentu saja harus tahu aturannya. Anda dapat menemukan kode untuk bagian penilaian skor di sini , harap dicatat bahwa implementasinya adalah untuk versi Skandinavia (Yatzy), dan implementasi kami mengasumsikan dadu diberikan dalam urutan urut.
sumber
Contoh ini sangat merindukan intinya. Kita berbicara tentang satu fungsi langsung di sini bukan desain perangkat lunak. Apakah ini agak rumit? ya, jadi Anda memecahnya. Dan Anda benar-benar tidak menguji setiap input yang mungkin dari 1, 1, 1, 1, 1 hingga 6, 6, 6, 6, 6, 6. Fungsi yang dimaksud tidak memerlukan pesanan, hanya kombinasi, yaitu AAABB.
Anda tidak perlu 200 tes logika terpisah. Anda bisa menggunakan satu set misalnya. Hampir semua bahasa pemrograman memiliki satu bawaan:
Dan jika Anda mendapatkan input yang bukan gulungan Yahtzee yang valid, Anda harus melempar seperti tidak ada hari esok.
sumber