Pengantar singkat untuk pertanyaan ini. Saya telah menggunakan TDD sekarang dan belakangan ini BDD selama lebih dari satu tahun sekarang. Saya menggunakan teknik seperti mengejek untuk membuat menulis tes saya lebih efisien. Akhir-akhir ini saya telah memulai proyek pribadi untuk menulis sedikit program pengelolaan uang untuk diri saya sendiri. Karena saya tidak punya kode warisan, itu adalah proyek yang sempurna untuk memulai dengan TDD. Sayangnya saya tidak mengalami banyak kesenangan dari TDD. Itu bahkan merusak kesenangan saya sehingga saya menyerah pada proyek.
Apa masalahnya? Yah, saya telah menggunakan pendekatan seperti TDD untuk membiarkan tes / persyaratan berevolusi desain program. Masalahnya adalah lebih dari setengah waktu pengembangan untuk tes menulis / refactor. Jadi pada akhirnya saya tidak ingin menerapkan lebih banyak fitur karena saya perlu refactor dan menulis ke banyak tes.
Di tempat kerja saya memiliki banyak kode warisan. Di sini saya menulis lebih banyak dan lebih banyak tes integrasi dan penerimaan dan lebih sedikit tes unit. Ini tampaknya bukan pendekatan yang buruk karena bug sebagian besar terdeteksi oleh tes penerimaan dan integrasi.
Gagasan saya adalah, pada akhirnya saya bisa menulis lebih banyak tes integrasi dan penerimaan daripada tes unit. Seperti yang saya katakan untuk mendeteksi bug, tes unit tidak lebih baik dari tes integrasi / penerimaan. Tes unit juga baik untuk desain. Karena saya sering menulis banyak dari mereka, kelas saya selalu dirancang agar dapat diuji dengan baik. Selain itu, pendekatan untuk membiarkan tes / persyaratan memandu desain dalam banyak kasus mengarah ke desain yang lebih baik. Keuntungan terakhir dari unit test adalah mereka lebih cepat. Saya telah menulis cukup tes integrasi untuk mengetahui, bahwa mereka bisa hampir secepat unit test.
Setelah saya mencari melalui web saya menemukan bahwa ada ide yang sangat mirip dengan saya yang disebutkan di sana - sini . Apa pendapatmu tentang ide ini?
Sunting
Menanggapi pertanyaan-pertanyaan satu contoh di mana desainnya bagus, tetapi saya membutuhkan refactoring besar untuk persyaratan berikutnya:
Pada awalnya ada beberapa persyaratan untuk menjalankan perintah tertentu. Saya menulis parser perintah yang dapat diperpanjang - yang mem-parsing perintah dari beberapa jenis command prompt dan memanggil yang benar pada model. Hasilnya diwakili dalam kelas model tampilan:
Tidak ada yang salah di sini. Semua kelas independen satu sama lain dan saya dapat dengan mudah menambahkan perintah baru, menampilkan data baru.
Persyaratan berikutnya adalah, bahwa setiap perintah harus memiliki representasi tampilan sendiri - semacam pratinjau hasil perintah. Saya mendesain ulang program untuk mencapai desain yang lebih baik untuk persyaratan baru:
Ini juga bagus karena sekarang setiap perintah memiliki model tampilan sendiri dan oleh karena itu pratinjau sendiri.
Masalahnya adalah, bahwa parser perintah diubah untuk menggunakan parsing berbasis token dari perintah dan dilucuti dari kemampuannya untuk mengeksekusi perintah. Setiap perintah memiliki model tampilan sendiri dan model tampilan data hanya mengetahui model tampilan perintah saat ini daripada mengetahui data yang harus ditampilkan.
Yang ingin saya ketahui pada saat ini adalah, jika desain baru tidak melanggar persyaratan yang ada. Saya tidak perlu mengubah APA SAJA dari tes penerimaan saya. Saya harus memperbaiki atau menghapus hampir SETIAP unit test, yang merupakan tumpukan pekerjaan.
Apa yang ingin saya tunjukkan di sini adalah situasi umum yang sering terjadi selama pengembangan. Tidak ada masalah dengan desain lama atau baru, mereka hanya berubah secara alami dengan persyaratan - bagaimana saya memahaminya, ini adalah salah satu keunggulan TDD, bahwa desain berkembang.
Kesimpulan
Terima kasih atas semua jawaban dan diskusi. Dalam ringkasan diskusi ini saya telah memikirkan suatu pendekatan yang akan saya uji dengan proyek saya berikutnya.
- Pertama-tama saya menulis semua tes sebelum menerapkan hal seperti yang selalu saya lakukan.
- Untuk persyaratan, pada awalnya saya menulis beberapa tes penerimaan yang menguji seluruh program. Lalu saya menulis beberapa tes integrasi untuk komponen di mana saya perlu menerapkan persyaratan. Jika ada komponen yang bekerja sama erat dengan komponen lain untuk mengimplementasikan persyaratan ini, saya juga akan menulis beberapa tes integrasi di mana kedua komponen diuji bersama. Terakhir tetapi tidak kalah pentingnya jika saya harus menulis algoritma atau kelas lain dengan permutasi tinggi - misalnya serializer - saya akan menulis unit test untuk kelas khusus ini. Semua kelas lain tidak diuji tetapi tes unit apa pun.
- Untuk bug prosesnya dapat disederhanakan. Biasanya bug disebabkan oleh satu atau dua komponen. Dalam hal ini saya akan menulis satu tes integrasi untuk komponen yang menguji bug. Jika terkait dengan algoritma, saya hanya akan menulis unit test. Jika tidak mudah untuk mendeteksi komponen di mana bug terjadi, saya akan menulis tes penerimaan untuk menemukan bug - ini harus menjadi pengecualian.
sumber
Jawaban:
Ini membandingkan jeruk dan apel.
Tes integrasi, tes penerimaan, tes unit, tes perilaku - semuanya adalah tes dan semuanya akan membantu Anda meningkatkan kode Anda, tetapi semuanya juga sangat berbeda.
Saya akan membahas masing-masing tes yang berbeda menurut pendapat saya dan mudah-mudahan menjelaskan mengapa Anda membutuhkan perpaduan dari semuanya:
Tes integrasi:
Sederhananya, uji apakah bagian komponen yang berbeda dari sistem Anda terintegrasi dengan benar - misalnya - mungkin Anda mensimulasikan permintaan layanan web dan memeriksa apakah hasilnya kembali. Saya biasanya akan menggunakan data statis (ish) nyata dan dependensi mengejek untuk memastikan bahwa itu dapat diverifikasi secara konsisten.
Tes penerimaan:
Tes penerimaan harus langsung berkorelasi dengan kasus penggunaan bisnis. Itu bisa sangat besar ("perdagangan dikirimkan dengan benar") atau kecil ("filter berhasil memfilter daftar") - tidak masalah; yang penting adalah bahwa itu harus secara eksplisit dikaitkan dengan kebutuhan pengguna tertentu. Saya suka fokus pada hal ini untuk pengembangan yang digerakkan oleh tes karena itu berarti kami memiliki manual referensi yang baik untuk cerita pengguna untuk dev dan qa untuk diverifikasi.
Tes unit:
Untuk unit kecil yang terpisah dari fungsionalitas yang mungkin atau mungkin tidak membentuk cerita pengguna tersendiri dengan sendirinya - misalnya, cerita pengguna yang mengatakan bahwa kami mengambil semua pelanggan ketika kami mengakses halaman web tertentu dapat menjadi tes penerimaan (simulasikan mengenai web halaman dan memeriksa respons) tetapi juga dapat berisi beberapa tes unit (memverifikasi bahwa izin keamanan diperiksa, memverifikasi bahwa koneksi database menanyakan dengan benar, memverifikasi bahwa kode apa pun yang membatasi jumlah hasil dieksekusi dengan benar) - ini semua "unit test" itu bukan tes penerimaan lengkap.
Tes perilaku:
Tetapkan apa yang seharusnya mengalir dari aplikasi jika input tertentu. Misalnya, "ketika koneksi tidak dapat dibuat, verifikasi bahwa sistem mencoba kembali koneksi." Sekali lagi, ini tidak mungkin menjadi tes penerimaan penuh tetapi masih memungkinkan Anda untuk memverifikasi sesuatu yang bermanfaat.
Ini semua menurut saya melalui banyak pengalaman menulis tes; Saya tidak suka fokus pada pendekatan buku teks - melainkan, fokus pada apa yang memberikan nilai tes Anda.
sumber
TL; DR: Selama memenuhi kebutuhan Anda, ya.
Saya telah melakukan pengembangan Acceptance Test Driven Development (ATDD) selama bertahun-tahun sekarang. Itu bisa sangat sukses. Ada beberapa hal yang perlu diperhatikan.
Sekarang manfaatnya
Seperti biasa, terserah Anda untuk melakukan analisis dan mencari tahu apakah praktik ini sesuai dengan situasi Anda. Tidak seperti banyak orang, saya tidak berpikir ada jawaban yang tepat dan ideal. Itu akan tergantung pada kebutuhan dan persyaratan Anda.
sumber
Tes unit bekerja paling baik ketika antarmuka publik dari komponen yang digunakan tidak terlalu sering berubah. Ini berarti, ketika komponen sudah dirancang dengan baik (misalnya, mengikuti prinsip SOLID).
Jadi mempercayai desain yang baik hanya "berevolusi" dari "melempar" banyak tes unit pada suatu komponen adalah kekeliruan. TDD bukan "guru" untuk desain yang baik, itu hanya dapat membantu sedikit untuk memverifikasi bahwa aspek-aspek tertentu dari desain itu baik (terutama testabilitas).
Ketika kebutuhan Anda berubah, dan Anda harus mengubah bagian dalam komponen, dan ini akan merusak 90% dari pengujian unit Anda, jadi Anda harus sering-sering refactor, maka desain yang paling mungkin tidak begitu baik.
Jadi saran saya adalah: pikirkan desain komponen yang telah Anda buat, dan bagaimana Anda dapat membuatnya lebih mengikuti prinsip buka / tutup. Ide yang terakhir adalah untuk memastikan fungsionalitas komponen Anda dapat diperpanjang kemudian tanpa mengubahnya (dan dengan demikian tidak melanggar API komponen yang digunakan oleh unit test Anda). Komponen-komponen seperti itu dapat (dan harus) dicakup oleh tes unit test, dan pengalaman itu seharusnya tidak menyakitkan seperti yang telah Anda gambarkan.
Ketika Anda tidak dapat segera membuat desain seperti itu, tes penerimaan dan integrasi mungkin memang merupakan awal yang lebih baik.
EDIT: Terkadang desain komponen Anda bisa baik-baik saja, tetapi desain unit test Anda dapat menyebabkan masalah . Contoh sederhana: Anda ingin menguji metode "MyMethod" dari kelas X dan menulis
(anggap nilai memiliki semacam makna).
Asumsikan lebih lanjut, bahwa dalam kode produksi hanya ada satu panggilan
X.MyMethod
. Sekarang, untuk persyaratan baru, metode "MyMethod" membutuhkan parameter tambahan (misalnya, sesuatu seperticontext
), yang tidak dapat dihilangkan. Tanpa tes unit, seseorang harus memperbaiki kode panggilan hanya di satu tempat. Dengan tes unit, seseorang harus merefleksikan 500 tempat.Tetapi penyebabnya di sini bukanlah unit test itu sendiri, itu hanya fakta bahwa panggilan yang sama untuk "X.Metode" diulangi lagi dan lagi, tidak secara ketat mengikuti prinsip "Jangan Ulangi Diri Sendiri (KERING). Jadi solusinya di sini adalah untuk meletakkan data pengujian dan nilai-nilai yang diharapkan terkait dalam daftar dan menjalankan panggilan ke "MyMethod" dalam satu lingkaran (atau, jika alat pengujian mendukung apa yang disebut "tes drive data", untuk menggunakan fitur itu). Ini mengurangi jumlah tempat yang akan diubah dalam tes unit ketika tanda tangan metode berubah menjadi 1 (berlawanan dengan 500).
Dalam kasus dunia nyata Anda, situasinya mungkin lebih kompleks, tetapi saya harap Anda mendapatkan idenya - ketika pengujian unit Anda menggunakan API komponen yang tidak Anda ketahui jika dapat berubah, pastikan Anda mengurangi jumlahnya panggilan ke API tersebut seminimal mungkin.
sumber
X x= new X(); AssertTrue(x.MyMethod(12,"abc"))
sebelum benar-benar menerapkan metode ini. Menggunakan desain dimuka, Anda dapat menulisclass X{ public bool MyMethod(int p, string q){/*...*/}}
terlebih dahulu, dan menulis tes nanti. Dalam kedua kasus, Anda telah membuat keputusan desain yang sama. Jika keputusan itu baik atau buruk, TDD tidak akan memberi tahu Anda.Ya, tentunya.
Pertimbangkan ini:
Lihat perbedaan keseluruhan ....
Isu ini merupakan salah satu cakupan kode, jika Anda dapat mencapai tes penuh dari semua kode Anda menggunakan pengujian integrasi / penerimaan, maka tidak ada masalah. Kode Anda diuji. Itulah tujuannya.
Saya pikir Anda mungkin perlu mencampurnya, karena setiap proyek berbasis TDD akan memerlukan beberapa pengujian integrasi hanya untuk memastikan bahwa semua unit benar-benar bekerja dengan baik bersama-sama (saya tahu dari pengalaman bahwa 100% unit kode basis yang diuji tidak selalu bekerja ketika Anda menempatkan mereka semua!)
Masalahnya benar-benar turun ke kemudahan pengujian, men-debug kegagalan, dan memperbaikinya. Beberapa orang menemukan unit test mereka sangat bagus dalam hal ini, mereka kecil dan sederhana dan kegagalan mudah dilihat, tetapi kerugiannya adalah Anda harus mengatur ulang kode Anda agar sesuai dengan alat tes unit, dan menulis sangat banyak dari mereka. Tes integrasi lebih sulit untuk ditulis untuk mencakup banyak kode, dan Anda mungkin harus menggunakan teknik seperti masuk untuk men-debug setiap kegagalan (meskipun, saya akan mengatakan Anda harus melakukan ini, Anda tidak dapat unit kegagalan pengujian ketika di tempat!).
Bagaimanapun, Anda masih mendapatkan kode yang diuji, Anda hanya perlu memutuskan mekanisme mana yang lebih cocok untuk Anda. (Saya akan menggunakan sedikit campuran, unit menguji algoritma yang kompleks, dan mengintegrasikan menguji sisanya).
sumber
Saya pikir itu ide yang mengerikan.
Karena tes penerimaan dan tes integrasi menyentuh bagian yang lebih luas dari kode Anda untuk menguji target tertentu, mereka akan memerlukan lebih banyak refactoring dari waktu ke waktu, tidak kurang. Lebih buruk lagi, karena mereka mencakup bagian luas dari kode, mereka meningkatkan waktu yang Anda habiskan untuk melacak penyebab root karena Anda memiliki area yang lebih luas untuk dicari.
Tidak, Anda biasanya harus menulis tes unit lebih banyak kecuali Anda memiliki aplikasi aneh di 90% UI atau sesuatu yang aneh untuk pengujian unit. Rasa sakit yang Anda hadapi bukan dari tes unit, tetapi melakukan tes pengembangan pertama. Secara umum, Anda hanya harus menghabiskan 1/3 waktu Anda di sebagian besar tes menulis. Lagi pula, mereka ada di sana untuk melayani Anda, bukan sebaliknya.
sumber
"Kemenangan" dengan TDD, adalah bahwa setelah tes telah ditulis, mereka dapat otomatis. Sisi lain adalah bahwa ia dapat mengkonsumsi sebagian besar waktu pengembangan. Apakah ini benar-benar memperlambat seluruh proses turun adalah diperdebatkan Argumennya adalah bahwa pengujian dimuka mengurangi jumlah kesalahan yang harus diperbaiki pada akhir siklus pengembangan.
Di sinilah BDD muncul karena perilaku dapat dimasukkan dalam pengujian unit sehingga prosesnya secara definisi kurang abstrak dan lebih nyata.
Jelas, jika jumlah waktu tak terbatas tersedia, Anda akan melakukan sebanyak mungkin pengujian berbagai varietas. Namun, waktu pada umumnya terbatas dan pengujian berkelanjutan hanya efektif biaya sampai titik tertentu.
Ini semua mengarah pada kesimpulan bahwa tes yang memberikan nilai paling banyak harus berada di depan proses. Ini sendiri tidak secara otomatis mendukung satu jenis pengujian di atas yang lain - lebih dari itu setiap kasus harus diambil berdasarkan kelebihannya.
Jika Anda menulis widget baris perintah untuk penggunaan pribadi, Anda terutama akan tertarik pada pengujian unit. Sedangkan layanan web mengatakan, akan membutuhkan sejumlah besar pengujian integrasi / perilaku.
Sementara sebagian besar jenis tes berkonsentrasi pada apa yang bisa disebut "garis balap" yaitu menguji apa yang dibutuhkan oleh bisnis saat ini, pengujian unit sangat baik untuk menghilangkan bug halus yang dapat muncul pada fase pengembangan selanjutnya. Karena ini adalah manfaat yang tidak dapat diukur dengan mudah, ini sering diabaikan.
sumber
Ini adalah titik kunci, dan bukan hanya "keunggulan terakhir". Ketika proyek semakin besar dan lebih besar, tes penerimaan integrasi Anda menjadi semakin lambat. Dan di sini, maksud saya sangat lambat sehingga Anda akan berhenti mengeksekusi mereka.
Tentu saja, tes unit menjadi lebih lambat juga, tetapi mereka masih lebih dari urutan besarnya lebih cepat. Misalnya, dalam proyek saya sebelumnya (c ++, sekitar 600 kLOC, 4000 unit test dan 200 tes integrasi), butuh sekitar satu menit untuk menjalankan semua dan lebih dari 15 untuk menjalankan tes integrasi. Untuk membangun dan menjalankan tes unit untuk bagian yang sedang diubah, akan memakan waktu rata-rata kurang dari 30 detik. Ketika Anda bisa melakukannya dengan sangat cepat, Anda pasti ingin melakukannya setiap saat.
Hanya untuk memperjelas: Saya tidak mengatakan tidak menambahkan tes integrasi dan penerimaan, tetapi sepertinya Anda melakukan TDD / BDD dengan cara yang salah.
Ya, merancang dengan testability dalam pikiran akan membuat desain lebih baik.
Nah, ketika persyaratan berubah, Anda harus mengubah kode. Saya akan memberitahu Anda tidak menyelesaikan pekerjaan Anda jika Anda tidak menulis tes unit. Tapi ini tidak berarti Anda harus memiliki cakupan 100% dengan unit test - itu bukan tujuannya. Beberapa hal (seperti GUI, atau mengakses file, ...) bahkan tidak dimaksudkan untuk diuji unit.
Hasil dari ini adalah kualitas kode yang lebih baik, dan lapisan pengujian lainnya. Saya akan mengatakan itu layak.
Kami juga memiliki beberapa tes penerimaan 1000-an, dan itu akan memakan waktu seminggu penuh untuk melaksanakan semua.
sumber