Baru-baru ini saya menulis sepotong kecil kode yang akan menunjukkan dengan cara yang ramah-manusiawi berapa umur suatu peristiwa. Misalnya, ini dapat mengindikasikan bahwa acara tersebut terjadi "Tiga minggu lalu" atau "Sebulan yang lalu" atau "Kemarin."
Persyaratannya relatif jelas dan ini adalah kasus yang sempurna untuk pengembangan yang digerakkan oleh tes. Saya menulis tes satu per satu, menerapkan kode untuk lulus setiap tes, dan semuanya tampak berfungsi dengan baik. Sampai bug muncul dalam produksi.
Inilah bagian kode yang relevan:
now = datetime.datetime.utcnow()
today = now.date()
if event_date.date() == today:
return "Today"
yesterday = today - datetime.timedelta(1)
if event_date.date() == yesterday:
return "Yesterday"
delta = (now - event_date).days
if delta < 7:
return _number_to_text(delta) + " days ago"
if delta < 30:
weeks = math.floor(delta / 7)
if weeks == 1:
return "A week ago"
return _number_to_text(weeks) + " weeks ago"
if delta < 365:
... # Handle months and years in similar manner.
Tes sedang memeriksa kasus dari suatu peristiwa yang terjadi hari ini, kemarin, empat hari yang lalu, dua minggu yang lalu, seminggu yang lalu, dll, dan kode dibangun sesuai.
Apa yang saya lewatkan adalah bahwa suatu peristiwa dapat terjadi sehari sebelum kemarin, sementara menjadi satu hari yang lalu: misalnya suatu peristiwa yang terjadi dua puluh enam jam yang lalu akan menjadi satu hari yang lalu, sementara tidak persis kemarin jika sekarang jam 1 pagi. Lebih tepatnya, itu satu poin sesuatu, tetapi karena delta
bilangan bulat, itu hanya satu. Dalam hal ini, aplikasi menampilkan "One days ago," yang jelas-jelas tidak terduga dan tidak ditangani dalam kode. Itu dapat diperbaiki dengan menambahkan:
if delta == 1:
return "A day ago"
setelah menghitung delta
.
Sementara satu-satunya konsekuensi negatif dari bug adalah saya menghabiskan setengah jam bertanya-tanya bagaimana kasus ini bisa terjadi (dan percaya bahwa itu ada hubungannya dengan zona waktu, meskipun penggunaan seragam UTC dalam kode), keberadaannya mengganggu saya. Ini menunjukkan bahwa:
- Sangat mudah untuk melakukan kesalahan logis bahkan dalam kode sumber yang sederhana.
- Pengembangan yang digerakkan oleh tes tidak membantu.
Yang juga mengkhawatirkan adalah saya tidak bisa melihat bagaimana bug seperti itu bisa dihindari. Selain berpikir lebih banyak sebelum menulis kode, satu-satunya cara yang dapat saya pikirkan adalah menambahkan banyak pernyataan untuk kasus-kasus yang saya yakini tidak akan pernah terjadi (seperti saya percaya bahwa sehari yang lalu adalah kemarin), dan kemudian mengulang setiap detik untuk sepuluh tahun terakhir, memeriksa setiap pelanggaran pernyataan, yang tampaknya terlalu rumit.
Bagaimana saya bisa menghindari membuat bug ini sejak awal?
sumber
Jawaban:
Ini adalah jenis kesalahan yang biasanya Anda temukan di langkah refactor red / green / refactor. Jangan lupakan langkah itu! Pertimbangkan refactor seperti berikut (belum diuji):
Di sini Anda telah membuat 3 fungsi pada level abstraksi yang lebih rendah yang jauh lebih kohesif dan lebih mudah untuk diuji secara terpisah. Jika Anda meninggalkan rentang waktu yang Anda inginkan, itu akan menonjol seperti jempol sakit dalam fungsi pembantu yang lebih sederhana. Juga, dengan menghapus duplikasi, Anda mengurangi potensi kesalahan. Anda benar-benar harus menambahkan kode untuk mengimplementasikan kasing Anda.
Kasus uji lain yang lebih halus juga lebih mudah terlintas dalam pikiran ketika melihat bentuk refactored seperti ini. Misalnya, apa yang harus
best_unit
dilakukan jikadelta
negatif?Dengan kata lain, refactoring tidak hanya untuk membuatnya cantik. Itu memudahkan manusia untuk menemukan kesalahan yang tidak bisa dilakukan oleh kompiler.
sumber
pluralize
hanya ada bekerja untuk subset dari kata-kata bahasa Inggris akan menjadi kewajiban.pluralize
menggunakannum
danunit
untuk membangun semacam kunci untuk menarik string format dari beberapa tabel / file sumber daya. ATAU Anda mungkin perlu menulis ulang logika secara lengkap, karena Anda memerlukan unit yang berbeda ;-)Sepertinya itu memang membantu, hanya saja Anda tidak memiliki tes untuk skenario "sehari yang lalu". Agaknya, Anda menambahkan tes setelah kasus ini ditemukan; ini masih TDD, ketika bug ditemukan Anda menulis unit-test untuk mendeteksi bug, kemudian memperbaikinya.
Jika Anda lupa menulis tes untuk suatu perilaku, TDD tidak ada yang membantu Anda; Anda lupa menulis tes dan karena itu jangan menulis implementasinya.
sumber
datetime.utcnow()
keluar dari fungsi, dan sebagai gantinya lulusnow
sebagai argumen (direproduksi) sebagai gantinya.Tes tidak akan banyak membantu jika masalah tidak didefinisikan dengan baik. Anda jelas mencampur hari kalender dengan hari yang dihitung dalam jam. Jika Anda tetap pada hari-hari kalender, maka pada jam 1 pagi, 26 jam yang lalu bukan kemarin. Dan jika Anda bertahan berjam-jam, maka 26 jam yang lalu berputar menjadi 1 hari yang lalu terlepas dari waktu.
sumber
Kamu tidak bisa TDD sangat bagus untuk melindungi Anda dari kemungkinan masalah yang Anda ketahui. Tidak membantu jika Anda mengalami masalah yang tidak pernah Anda pertimbangkan. Taruhan terbaik Anda adalah meminta orang lain menguji sistem, mereka mungkin menemukan kasus tepi yang tidak pernah Anda pertimbangkan.
Bacaan terkait: Apakah mungkin untuk mencapai kondisi zero bug absolut untuk perangkat lunak skala besar?
sumber
Ada dua pendekatan yang biasanya saya ambil yang menurut saya bisa membantu.
Pertama, saya mencari kasing tepi. Ini adalah tempat di mana perilaku berubah. Dalam kasus Anda, perilaku berubah pada beberapa titik di sepanjang urutan bilangan bulat positif. Ada kasus tepi di nol, di satu, di tujuh, dll. Saya kemudian akan menulis kasus uji di dan di sekitar kasus tepi. Saya akan menguji kasus pada -1 hari, 0 hari, 1 jam, 23 jam, 24 jam, 25 jam, 6 hari, 7 hari, 8 hari, dll.
Hal kedua yang saya cari adalah pola perilaku. Dalam logika Anda selama berminggu-minggu, Anda memiliki penanganan khusus selama satu minggu. Anda mungkin memiliki logika yang serupa di setiap interval lain yang tidak ditampilkan. Logika ini tidak ada selama berhari-hari. Saya akan melihatnya dengan kecurigaan sampai saya bisa menjelaskan mengapa kasus itu berbeda, atau saya menambahkan logikanya.
sumber
Anda tidak dapat menangkap kesalahan logis yang ada dalam persyaratan Anda dengan TDD. Tapi tetap saja, TDD membantu. Anda menemukan kesalahan, dan menambahkan test case. Tetapi pada dasarnya, TDD hanya memastikan bahwa kode tersebut sesuai dengan model mental Anda. Jika model mental Anda cacat, test case tidak akan menangkapnya.
Tetapi perlu diingat, saat memperbaiki bug, kasus-kasus pengujian Anda sudah memastikan tidak ada, perilaku yang berfungsi rusak. Itu sangat penting, mudah untuk memperbaiki satu bug tetapi memperkenalkan bug lainnya.
Untuk menemukan kesalahan itu sebelumnya, Anda biasanya mencoba menggunakan kasus uji berbasis kelas ekuivalen. menggunakan prinsip itu, Anda akan memilih satu kasus dari setiap kelas kesetaraan, dan kemudian semua kasus tepi.
Anda akan memilih tanggal mulai hari ini, kemarin, beberapa hari yang lalu, tepat satu minggu yang lalu dan beberapa minggu yang lalu sebagai contoh dari setiap kelas kesetaraan. Saat menguji tanggal, Anda juga akan memastikan bahwa pengujian Anda tidak menggunakan tanggal sistem, tetapi menggunakan tanggal yang telah ditentukan sebelumnya untuk perbandingan. Ini juga akan menyoroti beberapa kasus tepi: Anda akan memastikan untuk menjalankan tes Anda pada waktu yang sewenang-wenang hari itu, Anda akan menjalankannya dengan langsung setelah tengah malam, langsung sebelum tengah malam dan bahkan langsung di tengah malam. Ini berarti untuk setiap tes, akan ada empat kali dasar diuji.
Maka Anda akan secara sistematis menambahkan kasus tepi ke semua kelas lainnya. Anda memiliki tes untuk hari ini. Jadi tambahkan waktu sesaat sebelum dan sesudah perilaku harus beralih. Sama untuk kemarin. Sama untuk satu minggu yang lalu dll.
Kemungkinannya adalah dengan menyebutkan semua kasing secara sistematis dan menuliskan kasing untuk mereka, Anda mengetahui bahwa spesifikasi Anda kurang detail dan menambahkannya. Perhatikan bahwa menangani kurma adalah sesuatu yang orang sering salah, karena orang sering lupa menulis tes mereka sehingga mereka dapat dijalankan dengan waktu yang berbeda.
Namun, perlu diketahui bahwa sebagian besar dari apa yang saya tulis tidak ada hubungannya dengan TDD. Ini tentang menuliskan kelas kesetaraan dan memastikan spesifikasi Anda sendiri cukup rinci tentang mereka. Itu adalah proses yang Anda meminimalkan kesalahan logis. TDD memastikan kode Anda sesuai dengan model mental Anda.
Sulit untuk membuat test case . Pengujian berbasis kelas kesetaraan bukanlah akhir dari semuanya, dan dalam beberapa kasus dapat secara signifikan meningkatkan jumlah kasus pengujian. Di dunia nyata, menambahkan semua tes itu seringkali tidak layak secara ekonomi (meskipun secara teori, itu harus dilakukan).
sumber
Kenapa tidak? Ini sepertinya ide yang bagus!
Menambahkan kontrak (pernyataan) ke kode adalah cara yang cukup solid untuk meningkatkan kebenarannya. Secara umum kami menambahkannya sebagai prasyarat pada entri fungsi dan postkondisi pada pengembalian fungsi. Sebagai contoh, kita bisa menambahkan postcondition bahwa semua nilai-nilai kembali adalah salah bentuk "A [unit] lalu" atau "[jumlah] [unit] s lalu". Ketika dilakukan dengan cara yang disiplin, ini mengarah ke desain dengan kontrak , dan merupakan salah satu cara paling umum untuk menulis kode jaminan tinggi.
Secara kritis, kontrak tidak dimaksudkan untuk diuji; mereka hanya spesifikasi sebanyak kode Anda seperti tes Anda. Namun, Anda dapat menguji melalui kontrak: hubungi kode dalam tes Anda dan, jika tidak ada kontrak yang menimbulkan kesalahan, tes akan berlalu. Mengitari setiap detik dalam sepuluh tahun terakhir agak banyak. Tetapi kita dapat meningkatkan gaya pengujian lain yang disebut pengujian berbasis properti .
Dalam PBT alih-alih menguji untuk output spesifik dari kode, Anda menguji bahwa output mematuhi beberapa properti. Misalnya, satu properti dari
reverse()
fungsi adalah bahwa untuk setiap daftarl
,reverse(reverse(l)) = l
. Kelebihan dari tes menulis seperti ini adalah Anda dapat memiliki mesin PBT menghasilkan beberapa ratus daftar acak (dan beberapa yang patologis) dan memeriksa mereka semua memiliki properti ini. Jika ada yang tidak , mesin "menyusutkan" kasing gagal untuk menemukan daftar minimal yang merusak kode Anda. Sepertinya Anda sedang menulis Python, yang memiliki Hipotesis sebagai kerangka PBT utama.Jadi, jika Anda ingin cara yang baik untuk menemukan kasus tepi yang lebih rumit yang mungkin tidak Anda pikirkan, menggunakan kontrak dan pengujian berbasis properti bersama-sama akan banyak membantu. Ini tidak menggantikan tes unit penulisan, tentu saja, tetapi menambahnya, yang benar-benar yang terbaik yang bisa kita lakukan sebagai insinyur.
sumber
/(today)|(yesterday)|([2-6] days ago)|...
) dan kemudian Anda dapat menjalankan proses dengan input yang dipilih secara acak sampai Anda menemukan satu yang tidak berada dalam set output yang diharapkan. Mengambil pendekatan ini akan menangkap bug ini, dan tidak perlu menyadari bahwa bug itu mungkin ada sebelumnya.Ini adalah contoh di mana menambahkan sedikit modularitas akan bermanfaat. Jika segmen kode rawan kesalahan digunakan beberapa kali, itu praktik yang baik untuk membungkusnya dalam suatu fungsi jika memungkinkan.
sumber
TDD bekerja paling baik sebagai teknik jika orang yang menulis tes itu berlawanan. Ini sulit jika Anda bukan pemrograman pasangan, jadi cara lain untuk memikirkan ini adalah:
Ini adalah seni yang berbeda, yang berlaku untuk menulis kode yang benar dengan atau tanpa TDD, dan yang mungkin kompleks (jika tidak lebih) daripada benar-benar menulis kode. Ini adalah sesuatu yang perlu Anda latih, dan ini adalah sesuatu yang tidak ada jawaban tunggal, mudah, dan sederhana.
Teknik inti untuk menulis perangkat lunak yang tangguh, juga merupakan teknik inti untuk memahami cara menulis tes yang efektif:
Memahami prasyarat untuk suatu fungsi - status yang valid (yaitu asumsi apa yang Anda buat tentang status kelas, fungsi adalah metode) dan rentang parameter input yang valid - setiap tipe data memiliki kisaran nilai yang mungkin - subset yang akan ditangani oleh fungsi Anda.
Jika Anda tidak melakukan apa-apa selain secara eksplisit menguji asumsi ini pada entri fungsi, dan memastikan bahwa pelanggaran dicatat atau dilempar dan / atau kesalahan fungsi keluar tanpa penanganan lebih lanjut, Anda dapat dengan cepat mengetahui apakah perangkat lunak Anda gagal dalam produksi, membuatnya kuat dan toleransi kesalahan, dan mengembangkan keterampilan menulis tes permusuhan Anda.
NB. Ada seluruh literatur tentang Kondisi Pra dan Pasca, Invarian dan sebagainya, bersama dengan perpustakaan yang dapat menerapkannya menggunakan atribut. Secara pribadi saya bukan penggemar pergi begitu formal, tetapi nilainya melihat ke dalam.
sumber
Ini adalah salah satu fakta paling penting tentang pengembangan perangkat lunak: Sangatlah mustahil untuk menulis kode bebas bug.
TDD tidak akan menyelamatkan Anda dari memperkenalkan bug yang terkait dengan kasus pengujian yang tidak Anda pikirkan. Itu juga tidak akan menyelamatkan Anda dari menulis tes yang salah tanpa menyadarinya, kemudian menulis kode yang salah yang lulus tes kereta. Dan setiap teknik pengembangan perangkat lunak tunggal lainnya yang pernah dibuat memiliki lubang yang serupa. Sebagai pengembang, kami adalah manusia yang tidak sempurna. Pada akhirnya, tidak ada cara untuk menulis kode bebas bug 100%. Tidak pernah dan tidak akan pernah terjadi.
Ini bukan untuk mengatakan bahwa Anda harus menyerah. Walaupun tidak mungkin untuk menulis kode yang benar-benar sempurna, sangat mungkin untuk menulis kode yang memiliki sedikit bug yang muncul dalam kasus tepi yang langka sehingga perangkat lunak ini sangat praktis untuk digunakan. Perangkat lunak yang tidak menunjukkan perilaku kereta dalam praktek sangat banyak mungkin untuk menulis.
Tetapi menulis itu mengharuskan kita untuk merangkul fakta bahwa kita akan menghasilkan perangkat lunak kereta. Hampir setiap praktik pengembangan perangkat lunak modern pada tingkat tertentu dibangun untuk mencegah bug muncul di tempat pertama atau melindungi diri kita sendiri dari konsekuensi bug yang kita produksi:
Solusi utama untuk masalah yang Anda identifikasi bukan untuk melawan fakta bahwa Anda tidak dapat menjamin Anda akan menulis kode bebas bug, tetapi untuk menerimanya. Merangkul praktik terbaik industri di semua bidang proses pengembangan Anda, dan Anda akan secara konsisten memberikan kode kepada pengguna Anda yang, meskipun tidak cukup sempurna, lebih dari cukup kuat untuk pekerjaan itu.
sumber
Anda sama sekali tidak memikirkan kasus ini sebelumnya dan karena itu tidak memiliki ujian untuk itu.
Ini terjadi sepanjang waktu dan hanya normal. Selalu merupakan trade-off berapa banyak usaha yang Anda lakukan dalam menciptakan semua test case yang mungkin. Anda dapat menghabiskan waktu tanpa batas untuk mempertimbangkan semua kasus uji.
Untuk autopilot pesawat, Anda akan menghabiskan lebih banyak waktu daripada untuk alat sederhana.
Sering membantu untuk memikirkan rentang valid dari variabel input Anda dan menguji batas-batas ini.
Selain itu, jika penguji adalah orang yang berbeda dari pengembang, sering ditemukan kasus yang lebih signifikan.
sumber
Itu kesalahan logis lain dalam kode Anda yang belum Anda uji unit :) - metode Anda akan mengembalikan hasil yang salah untuk pengguna di zona waktu non-UTC. Anda harus mengonversi "sekarang" dan tanggal acara ke zona waktu lokal pengguna sebelum menghitung.
Contoh: Di Australia, sebuah peristiwa terjadi pada jam 9 pagi waktu setempat. Pada jam 11 pagi, itu akan ditampilkan sebagai "kemarin" karena tanggal UTC telah berubah.
sumber
Biarkan orang lain yang menulis tes. Dengan cara ini seseorang yang tidak terbiasa dengan implementasi Anda mungkin memeriksa situasi langka yang belum Anda pikirkan.
Jika memungkinkan, menyuntikkan kasus uji sebagai koleksi. Ini membuat menambahkan tes lain semudah menambahkan baris lain seperti
yield return new TestCase(...)
. Ini dapat mengarah pada pengujian eksplorasi , mengotomatiskan pembuatan kasus uji: "Mari kita lihat apa kode itu kembali untuk semua detik dari satu minggu yang lalu".sumber
Anda tampaknya berada di bawah kesalahpahaman bahwa jika semua tes Anda lulus, Anda tidak memiliki bug. Pada kenyataannya, jika semua tes Anda lulus, semua perilaku yang diketahui benar. Anda masih tidak tahu apakah perilaku yang tidak diketahui itu benar atau tidak.
Semoga Anda menggunakan cakupan kode dengan TDD Anda. Tambahkan tes baru untuk perilaku yang tidak terduga. Kemudian Anda dapat menjalankan hanya tes untuk perilaku tak terduga untuk melihat jalan apa yang sebenarnya diperlukan melalui kode. Setelah mengetahui perilaku saat ini, Anda dapat membuat perubahan untuk memperbaikinya, dan ketika semua tes lulus lagi, Anda akan tahu bahwa Anda telah melakukannya dengan benar.
Ini masih tidak berarti bahwa kode Anda bebas bug, hanya saja itu lebih baik dari sebelumnya, dan sekali lagi semua perilaku yang diketahui benar!
Menggunakan TDD dengan benar tidak berarti Anda akan menulis kode bebas bug, itu berarti Anda akan menulis lebih sedikit bug. Kamu bilang:
Apakah ini berarti bahwa perilaku lebih dari satu hari tetapi tidak kemarin ditentukan dalam persyaratan? Jika Anda melewatkan persyaratan tertulis, itu salah Anda. Jika Anda menyadari persyaratannya tidak lengkap saat Anda mengkodekannya, bagus untuk Anda! Jika semua orang yang mengerjakan persyaratan melewatkan kasus itu, Anda tidak lebih buruk daripada yang lain. Setiap orang membuat kesalahan, dan semakin halus mereka, semakin mudah untuk dilewatkan. Yang penting di sini adalah bahwa TDD tidak mencegah semua kesalahan!
sumber
Iya. Pengembangan yang digerakkan oleh tes tidak mengubah hal itu. Anda masih dapat membuat bug dalam kode aktual, dan juga dalam kode pengujian.
Oh, tapi ternyata berhasil! Pertama-tama, ketika Anda melihat bug Anda sudah memiliki kerangka kerja pengujian yang lengkap di tempat, dan hanya harus memperbaiki bug dalam tes (dan kode aktual). Kedua, Anda tidak tahu berapa banyak bug yang akan Anda miliki jika Anda tidak melakukan TDD pada awalnya.
Kamu tidak bisa Bahkan NASA belum menemukan cara untuk menghindari bug; kita manusia yang lebih rendah tentu tidak.
Itu adalah kekeliruan. Salah satu manfaat terbesar TDD adalah Anda dapat membuat kode dengan sedikit pemikiran, karena semua tes itu setidaknya menangkap regresi dengan cukup baik. Juga, bahkan, atau terutama dengan TDD, itu tidak diharapkan untuk memberikan kode bebas bug di tempat pertama (atau kecepatan pengembangan Anda hanya akan terhenti).
Ini jelas akan bertentangan dengan prinsip hanya mengkode apa yang sebenarnya Anda butuhkan saat ini. Anda pikir Anda perlu kasing itu, dan memang begitu. Itu adalah kode yang tidak penting; seperti yang Anda katakan tidak ada kerusakan kecuali Anda bertanya-tanya tentang hal itu selama 30 menit.
Untuk kode mission-critical, Anda sebenarnya bisa melakukan apa yang Anda katakan, tetapi tidak untuk kode standar sehari-hari Anda.
Kamu tidak. Anda percaya pada tes Anda untuk menemukan sebagian besar regresi; Anda tetap menggunakan siklus merah-hijau-refactor, menulis tes sebelum / selama pengkodean aktual, dan (penting!) Anda menerapkan jumlah minimum yang diperlukan untuk membuat sakelar merah-hijau (tidak lebih, tidak kurang). Ini akan berakhir dengan cakupan tes yang bagus, setidaknya yang positif.
Ketika, bukan jika, Anda menemukan bug, Anda menulis tes untuk mereproduksi bug itu, dan memperbaiki bug dengan jumlah pekerjaan paling sedikit untuk membuat tes tersebut berubah dari merah menjadi hijau.
sumber
Anda baru saja menemukan bahwa tidak peduli seberapa keras Anda mencoba, Anda tidak akan pernah dapat menangkap semua kemungkinan bug dalam kode Anda.
Jadi apa artinya ini adalah bahwa bahkan mencoba untuk menangkap semua bug adalah latihan sia-sia, dan jadi Anda hanya harus menggunakan teknik seperti TDD sebagai cara menulis kode yang lebih baik, kode yang memiliki lebih sedikit bug, bukan 0 bug.
Yang pada gilirannya berarti Anda harus menghabiskan lebih sedikit waktu menggunakan teknik ini, dan menghabiskan waktu yang dihemat itu bekerja pada cara-cara alternatif untuk menemukan bug yang lolos melalui jaring pengembangan.
alternatif seperti pengujian integrasi, atau tim pengujian, pengujian sistem, dan pencatatan dan analisis log tersebut.
Jika Anda tidak dapat menangkap semua bug, maka Anda harus memiliki strategi untuk mengurangi efek bug yang melewati Anda. Jika Anda tetap harus melakukan ini, maka lebih banyak melakukan upaya ini lebih masuk akal daripada mencoba (sia-sia) untuk menghentikan mereka di tempat pertama.
Lagi pula, tidak ada gunanya menghabiskan banyak waktu dalam tes menulis waktu dan hari pertama Anda memberikan produk Anda kepada pelanggan itu jatuh, terutama jika Anda kemudian tidak tahu bagaimana menemukan dan menyelesaikan bug itu. Resolusi bug post-mortem dan post-delivery sangat penting dan perlu lebih banyak perhatian daripada kebanyakan orang menghabiskan pada tes unit menulis. Simpan pengujian unit untuk bit yang rumit dan jangan mencoba untuk kesempurnaan di muka.
sumber
That in turn means you should spend less time using these techniques
- tetapi Anda baru saja mengatakan itu akan membantu dengan lebih sedikit bug ?!