Membuat objek yang menurut Anda perlu dilakukan ok pada tes pertama di TDD

15

Saya cukup baru untuk TDD dan saya mengalami kesulitan saat membuat tes pertama ketika datang sebelum salah satu kode implementasi. Tanpa kerangka kerja untuk kode implementasi saya bebas untuk menulis tes pertama saya namun saya ingin tetapi sepertinya selalu tercemar oleh cara berpikir Java / OO saya tentang masalah.

Sebagai contoh di Github ConwaysGameOfLifeExample saya tes pertama yang saya tulis (rule1_zeroNeighbours) saya mulai dengan membuat objek GameOfLife yang belum diimplementasikan; disebut metode set yang tidak ada, metode langkah yang tidak ada, metode get yang tidak ada, dan kemudian gunakan pernyataan.

Tes berkembang ketika saya menulis lebih banyak tes dan refactored, tapi awalnya terlihat seperti ini

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

Ini terasa aneh karena saya memaksakan desain implementasi berdasarkan bagaimana saya telah memutuskan pada tahap awal ini untuk menulis tes pertama ini.

Dalam cara Anda memahami TDD apakah ini ok? Saya tampaknya mengikuti prinsip-prinsip TDD / XP di mana tes dan implementasi saya berkembang seiring waktu dengan refactoring, dan jadi jika desain awal ini terbukti tidak berguna itu akan terbuka untuk berubah, tetapi rasanya seperti saya memaksakan arah pada solusi dengan memulai dengan cara ini.

Bagaimana lagi orang menggunakan TDD? Saya bisa melewati iterasi refactoring dengan memulai tanpa objek GameOfLife, hanya primitif dan metode statis tapi itu sepertinya terlalu dibuat-buat.

Encaitar
sumber
5
TDD tidak menggantikan perencanaan yang cermat atau pemilihan pola desain yang cermat. Yang mengatakan, sebelum Anda menulis implementasi apa pun untuk memenuhi beberapa tes pertama adalah waktu yang jauh lebih baik daripada setelah Anda menulis dependensi untuk mengetahui bahwa rencana Anda bodoh, bahwa Anda telah memilih pola yang salah, atau bahkan hanya bahwa itu canggung atau membingungkan untuk memanggil kelas dengan cara tes Anda menuntut.
svidgen

Jawaban:

9

Ini terasa aneh karena saya memaksakan desain implementasi berdasarkan bagaimana saya telah memutuskan pada tahap awal ini untuk menulis tes pertama ini.

Saya pikir ini adalah poin kunci dalam pertanyaan Anda, Apakah ini diinginkan atau tidak tergantung pada apakah Anda condong ke ide codeninja bahwa Anda harus merancang di muka kemudian menggunakan TDD untuk mengisi implementasi, atau gagasan durron bahwa tes harus dilibatkan dalam mengusir desain serta implementasinya.

Saya pikir yang mana yang Anda sukai (atau di mana Anda jatuh di tengah) adalah sesuatu yang perlu Anda temukan sendiri sebagai preferensi. Sangat berguna untuk memahami pro dan kontra dari setiap pendekatan. Mungkin ada banyak, tetapi saya akan mengatakan yang utama adalah:

Desain Awal Pro

  • Betapapun baiknya proses TDD dalam mengemudi desain, itu tidak sempurna. Tanpa tujuan konkret dalam benak kadang-kadang bisa menemui jalan buntu, dan setidaknya beberapa jalan buntu ini bisa dihindari dengan sedikit berpikir di muka tentang di mana Anda ingin berakhir. Artikel blog ini membuat argumen ini menggunakan contoh kata Angka Romawi, dan memiliki implementasi akhir yang agak bagus untuk ditunjukkan.

Desain Uji-Mengemudi Pro

  • Dengan membangun implementasi Anda di sekitar klien kode Anda (tes Anda), Anda mendapatkan kepatuhan YAGNI secara gratis, selama Anda tidak mulai menulis kasus uji yang tidak dibutuhkan. Secara umum, Anda mendapatkan API yang dirancang untuk digunakan oleh konsumen, yang pada akhirnya itulah yang Anda inginkan.

  • Gagasan menggambar sekelompok diagram UML sebelum menulis kode apa pun kemudian hanya mengisi celah itu bagus, tetapi jarang realistis. Dalam Code Complete Steve McConnell, desain terkenal digambarkan sebagai "masalah jahat" - masalah yang tidak dapat Anda pahami sepenuhnya tanpa terlebih dahulu setidaknya sebagian menyelesaikannya. Pasangkan ini dengan fakta bahwa masalah yang mendasarinya sendiri dapat berubah melalui perubahan persyaratan, dan model desain ini mulai merasa sedikit putus asa. Tes mengemudi memungkinkan Anda untuk hanya menggigit satu potong pekerjaan pada waktu-dalam desain, bukan hanya implementasi- dan tahu bahwa setidaknya untuk umur berubah menjadi hijau, tugas itu akan tetap up to date dan relevan.

Adapun contoh khusus Anda, seperti yang dikatakan durron, jika Anda memang menggunakan pendekatan mengusir desain dengan menulis tes paling sederhana, menggunakan antarmuka minimal, Anda mungkin akan mulai dengan antarmuka yang lebih sederhana daripada yang ada di cuplikan kode Anda .

Ben Aaronson
sumber
Tautannya sangat bagus, baca Ben. Terima kasih telah membagikannya.
RubberDuck
1
@RubberDuck Sama-sama! Sebenarnya saya tidak sepenuhnya setuju dengan itu, tetapi saya pikir itu sangat baik untuk memperdebatkan sudut pandang itu.
Ben Aaronson
1
Saya tidak yakin saya melakukannya juga, tetapi itu membuat kasusnya baik. Saya pikir jawaban yang tepat ada di suatu tempat di tengah. Anda harus punya rencana, tetapi tentu saja jika tes Anda terasa canggung, desain ulang. Pokoknya ... ++ pertunjukan bagus kacang tua.
RubberDuck
17

Untuk dapat menulis tes di tempat pertama, Anda harus merancang API yang kemudian akan Anda implementasikan. Anda sudah memulai dengan langkah yang salah dengan menulis tes Anda untuk membuat seluruh GameOfLife objek dan menggunakannya untuk mengimplementasikan tes Anda.

Dari Pengujian Unit Praktis dengan JUnit dan Mockito :

Pada awalnya Anda mungkin merasa canggung untuk menulis sesuatu yang bahkan tidak ada. Ini membutuhkan sedikit perubahan pada kebiasaan pengkodean Anda, tetapi setelah beberapa waktu Anda akan melihat bahwa ini adalah peluang desain yang bagus. Dengan menulis tes terlebih dahulu, Anda memiliki peluang untuk membuat API yang nyaman untuk digunakan oleh klien. Tes Anda adalah klien pertama dari API yang baru lahir. Inilah arti sebenarnya dari TDD: desain API.

Tes Anda tidak banyak berupaya untuk merancang API. Anda telah mengatur sistem stateful di mana semua fungsi terkandung dalam GameOfLifekelas luar .

Jika saya menulis aplikasi ini, sebagai gantinya saya akan memikirkan potongan yang ingin saya buat. Sebagai contoh, saya mungkin membuat Cellkelas, menulis tes untuk itu, sebelum pindah ke aplikasi yang lebih besar. Saya pasti akan membuat kelas untuk struktur data "tak terbatas di setiap arah" yang diperlukan untuk mengimplementasikan Conway dengan benar, dan mengujinya. Setelah semua itu selesai, saya akan berpikir tentang menulis kelas keseluruhan yang memiliki mainmetode dan sebagainya.

Sangat mudah untuk mengabaikan langkah "tulis tes yang gagal". Tetapi menulis tes gagal yang berfungsi seperti yang Anda inginkan adalah inti dari TDD.

durron597
sumber
1
Mengingat Sel hanya akan menjadi pembungkus untuk boolean, yang desain pasti akan lebih buruk untuk kinerja. Kecuali jika perlu diperluas di masa depan ke automata seluler lainnya dengan lebih dari dua negara?
user253751
2
@ Imibis Bercanda tentang detail. Anda bisa mulai dengan kelas yang mewakili kumpulan sel. Anda juga bisa memigrasi / menggabungkan kelas sel dan pengujiannya dengan kelas yang mewakili kumpulan sel nanti jika kinerja menjadi masalah.
Eric
@immibis Jumlah tetangga yang hidup dapat disimpan karena alasan kinerja. Jumlah kutu sel telah hidup, untuk alasan pewarnaan ..
Blorgbeard keluar
@immibis optimisasi dini adalah kejahatan ... Selain itu, menghindari obsesi primitif adalah cara terbaik untuk menulis kode kualitas yang baik, tidak peduli berapa banyak negara yang didukungnya. Lihatlah: jamesshore.com/Blog/PrimitiveObsession.html
Paul
0

Ada aliran pemikiran yang berbeda tentang hal ini.

Ada yang mengatakan: tes tidak mengkompilasi adalah kesalahan - perbaiki tulis kode produksi terkecil yang tersedia.

Some Say: Tidak apa-apa untuk menulis tes pertama periksa apakah itu menyebalkan (atau tidak) semut kemudian membuat kelas / metode yang hilang

Dengan pendekatan pertama Anda benar-benar dalam siklus merah-hijau-refactor. Dengan yang kedua Anda memiliki gambaran umum yang sedikit lebih luas tentang apa yang ingin Anda capai.

Terserah Anda untuk memilih cara kerja Anda. IMHO kedua pendekatan itu valid.

timoras
sumber
0

Bahkan ketika saya mengimplementasikan sesuatu dengan cara "hack it together", saya masih memikirkan kelas dan langkah-langkah yang akan terlibat dalam keseluruhan program. Jadi Anda sudah memikirkan ini dan menulis pemikiran desain ini sebagai tes pertama - itu hebat!

Sekarang tetap iterasi melalui kedua implementasi untuk memenuhi tes awal ini, dan kemudian tambahkan lebih banyak tes untuk meningkatkan dan memperluas desain.

Apa yang mungkin membantu Anda adalah menggunakan Timun atau sejenisnya untuk menulis tes Anda.

gbjbaanb
sumber
0

Sebelum Anda mulai menulis tes Anda, Anda harus memikirkan bagaimana merancang sistem Anda. Anda harus meluangkan banyak waktu selama fase desain Anda. Jika Anda melakukannya, Anda tidak akan mendapatkan kebingungan tentang TDD ini.

TDD hanyalah tautan pendekatan pengembangan : TDD
1. Tambahkan tes
2. Jalankan semua tes dan lihat apakah yang baru gagal
3. Tulis beberapa kode
4. Jalankan tes
5. Kode refactor
6. Ulangi

TDD membantu Anda untuk mencakup semua fitur yang diperlukan apa yang telah Anda rencanakan sebelum mulai mengembangkan perangkat lunak Anda. tautan: Manfaat

codeninja.sj
sumber
0

Saya tidak suka tes tingkat sistem yang ditulis dalam java atau C # karena alasan itu. Lihatlah SpecFlow untuk c # atau salah satu kerangka kerja pengujian berbasis Ketimun untuk java (mungkin JBehave). Maka tes Anda dapat terlihat seperti ini.

masukkan deskripsi gambar di sini

Dan Anda dapat mengubah desain objek Anda tanpa harus mengubah semua tes sistem Anda.

(Tes unit "normal" sangat bagus ketika menguji kelas tunggal.)

Apa perbedaan antara kerangka kerja BDD untuk Java?

Ian
sumber