Bagaimana cara menghindari kesalahan logis dalam kode, ketika TDD tidak membantu?


67

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 deltabilangan 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?


38
Dengan memiliki test case untuk itu? Itu tampak seperti bagaimana Anda menemukannya setelah itu, dan menyatu dengan TDD.
Kamis

63
Anda baru saja mengalami mengapa saya bukan penggemar uji coba pengembangan - dalam pengalaman saya sebagian besar bug yang tertangkap dalam produksi adalah skenario yang tidak dipikirkan oleh siapa pun. Pengembangan yang digerakkan oleh tes dan unit test tidak melakukan apa pun untuk ini. (Namun, tes unit memiliki nilai dalam mendeteksi bug yang diperkenalkan melalui pengeditan mendatang.)
Loren Pechtel

102
Ulangi setelah saya: "Tidak ada peluru perak, termasuk TDD." Tidak ada proses, tidak ada aturan, tidak ada algoritma yang dapat Anda ikuti secara robot untuk menghasilkan kode yang sempurna. Jika ada, kita dapat mengotomatiskan seluruh proses dan menyelesaikannya.
jpmc26

43
Selamat, Anda menemukan kembali kebijaksanaan lama bahwa tidak ada tes yang dapat membuktikan tidak adanya bug. Tetapi jika Anda mencari teknik untuk membuat cakupan yang lebih baik dari domain input yang mungkin, Anda perlu membuat analisis menyeluruh dari domain, kasus tepi dan kelas ekivalensi dari domain itu. Semua teknik lama yang sudah dikenal lama dikenal sebelum istilah TDD ditemukan.
Doc Brown

80
Saya tidak mencoba untuk menjadi angkuh, tetapi pertanyaan Anda tampaknya bisa diulangi sebagai "bagaimana saya memikirkan hal-hal yang saya tidak pikirkan?". Tidak yakin apa yang harus dilakukan dengan TDD.
Jared Smith

Jawaban:


57

Ini adalah jenis kesalahan yang biasanya Anda temukan di langkah refactor red / green / refactor. Jangan lupakan langkah itu! Pertimbangkan refactor seperti berikut (belum diuji):

def pluralize(num, unit):
    if num == 1:
        return unit
    else:
        return unit + "s"

def convert_to_unit(delta, unit):
    factor = 1
    if unit == "week":
        factor = 7 
    elif unit == "month":
        factor = 30
    elif unit == "year":
        factor = 365
    return delta // factor

def best_unit(delta):
    if delta < 7:
        return "day"
    elif delta < 30:
        return "week"
    elif delta < 365:
        return "month"
    else:
        return "year"

def human_friendly(event_date):
    date = event_date.date()
    today = now.date()
    yesterday = today - datetime.timedelta(1)
    if date == today:
        return "Today"
    elif date == yesterday:
        return "Yesterday"
    else:
        delta = (now - event_date).days
        unit = best_unit(delta)
        converted = convert_to_unit(delta, unit)
        pluralized = pluralize(converted, unit)
        return "{} {} ago".format(converted, pluralized)

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_unitdilakukan jika deltanegatif?

Dengan kata lain, refactoring tidak hanya untuk membuatnya cantik. Itu memudahkan manusia untuk menemukan kesalahan yang tidak bisa dilakukan oleh kompiler.


12
Langkah selanjutnya adalah menginternasionalkan, dan pluralizehanya ada bekerja untuk subset dari kata-kata bahasa Inggris akan menjadi kewajiban.
Deduplicator

@Dupuplikator pasti, tetapi kemudian tergantung pada bahasa / budaya mana yang Anda targetkan, Anda mungkin lolos dengan hanya memodifikasi pluralizemenggunakan numdan unituntuk 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 ;-)
Hulk

4
Masalah tetap ada bahkan dengan refactorisasi ini, yaitu bahwa "kemarin" tidak masuk akal pada jam-jam dini hari (tak lama setelah pukul 12:01). Dalam istilah ramah manusia, sesuatu yang terjadi pada pukul 11:59 malam tidak tiba-tiba berubah dari "hari ini" menjadi "kemarin" ketika jam berlalu lewat tengah malam. Alih-alih itu berubah dari "1 menit yang lalu" menjadi "2 menit yang lalu". "Hari ini" terlalu kasar dalam hal sesuatu yang terjadi tetapi beberapa menit yang lalu, dan "kemarin" penuh dengan masalah burung hantu malam.
David Hammen

@ Davidvidam Ini adalah masalah kegunaan dan itu tergantung pada seberapa tepat Anda harus. Ketika Anda ingin tahu setidaknya sampai jam, saya tidak akan berpikir "kemarin" itu baik. "24 jam yang lalu" jauh lebih jelas dan merupakan ekspresi manusia yang umum digunakan untuk menekankan jumlah jam. Komputer yang berusaha menjadi "ramah manusia" hampir selalu mendapatkan kesalahan ini dan menggeneralisasikannya menjadi "kemarin" yang terlalu kabur. Tetapi untuk mengetahui hal ini Anda harus mewawancarai pengguna untuk melihat apa yang mereka pikirkan. Untuk beberapa hal, Anda benar-benar menginginkan tanggal dan waktu yang tepat, jadi "kemarin" selalu salah.
Brandin

149

Pengembangan yang digerakkan oleh tes tidak membantu.

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.


2
Poin apa pun dapat dibuat bahwa jika pengembang tidak menggunakan tdd, mereka akan lebih mungkin untuk melewatkan kasus lain juga.
Caleb

75
Dan, di atas semua itu, pikirkan berapa banyak waktu yang dihemat saat mereka memperbaiki bug? Dengan menerapkan tes yang ada, mereka langsung tahu bahwa perubahan mereka tidak merusak perilaku yang ada. Dan mereka bebas menambahkan test case baru dan refactor tanpa harus menjalankan tes manual yang luas sesudahnya.
Caleb

15
TDD hanya sebagus tes yang ditulis.
Mindwin

Pengamatan lain: menambahkan tes untuk kasus ini akan meningkatkan desain, dengan memaksa kita untuk mengambil yang datetime.utcnow()keluar dari fungsi, dan sebagai gantinya lulus nowsebagai argumen (direproduksi) sebagai gantinya.
Toby Speight

114

suatu peristiwa yang terjadi dua puluh enam jam yang lalu adalah satu hari yang lalu

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.


45
Ini adalah poin yang bagus. Kehilangan persyaratan tidak selalu berarti bahwa proses Anda untuk implementasi gagal. Ini hanya berarti bahwa persyaratannya tidak didefinisikan dengan baik. (Atau Anda hanya membuat kesalahan manusia, yang akan terjadi dari waktu ke waktu)
Caleb

Inilah jawaban yang ingin saya sampaikan. Saya akan mendefinisikan spek sebagai "jika acara adalah hari kalender ini, tampilkan delta dalam beberapa jam. Lain gunakan tanggal hanya untuk menentukan delta" Jam pengujian hanya berguna dalam satu hari, jika di luar itu resolusi Anda dimaksudkan menjadi hari.
Baldrickk

1
Saya suka jawaban ini karena menunjukkan masalah sebenarnya: poin dalam waktu dan tanggal adalah dua jumlah yang berbeda. Mereka terkait tetapi ketika Anda mulai membandingkan mereka, segalanya berjalan cepat ke selatan. Dalam pemrograman, logika tanggal dan waktu adalah beberapa hal tersulit untuk diluruskan. Saya benar-benar tidak suka bahwa banyak implementasi tanggal pada dasarnya menyimpan tanggal sebagai titik waktu 0:00. Itu membuat banyak kebingungan.
Pieter B

38

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?


2
Memiliki tes yang ditulis oleh orang lain selain dari pengembang selalu merupakan ide yang baik, itu berarti bahwa kedua belah pihak perlu mengabaikan kondisi input yang sama untuk bug untuk membuatnya menjadi produksi.
Michael Kay

35

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.


9
Ini adalah bagian yang sangat penting dari TDD yang sering diabaikan dan saya jarang melihat dibicarakan dalam artikel dan panduan - sangat penting untuk menguji kasus tepi dan kondisi batas karena saya menemukan bahwa sumber 90% bug - dari-oleh -satu kesalahan, over dan underflow, hari terakhir bulan itu, bulan terakhir tahun ini, tahun kabisat dll.
GoatInTheMachine

2
@GoatInTheMachine - dan 90% dari 90% bug tersebut ada di sekitar transisi waktu musim panas ..... Hahaha
Caleb

1
Pertama-tama Anda dapat membagi input yang mungkin dalam kelas ekivalensi dan kemudian menentukan kasus tepi di perbatasan kelas. Tentu saja itu adalah upaya yang mungkin lebih besar dari upaya pengembangan; apakah itu layak tergantung pada seberapa pentingkah mengirimkan perangkat lunak tanpa kesalahan, batas waktu, dan berapa banyak uang serta kesabaran yang Anda miliki.
Peter - Pasang kembali Monica

2
Ini jawaban yang benar. Banyak aturan bisnis mengharuskan Anda untuk membagi berbagai nilai ke interval di mana mereka harus ditangani dengan cara yang berbeda.
abuzittin gillifirca

14

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).


12

Satu-satunya cara yang dapat saya pikirkan adalah menambahkan banyak pernyataan untuk kasus-kasus yang saya yakini tidak akan pernah terjadi (seperti yang saya yakini sehari lalu adalah kemarin), dan kemudian mengulang setiap detik selama sepuluh tahun terakhir, memeriksa setiap pelanggaran pernyataan, yang tampaknya terlalu rumit.

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 daftar l, 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.


2
Ini adalah solusi tepat untuk masalah seperti ini. Himpunan output yang valid mudah untuk didefinisikan (Anda dapat memberikan ekspresi reguler dengan sangat sederhana, seperti /(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.
Jules

@ Jules Lihat juga pemeriksaan / pengujian properti . Saya biasanya menulis tes properti selama pengembangan, untuk mencakup sebanyak mungkin kasus yang tidak terduga dan memaksa saya untuk memikirkan properti umum / invarian. Saya menyimpan tes satu kali untuk regresi dan semacamnya (yang mana masalah penulis adalah contoh)
Warbo

1
Jika Anda melakukan banyak pengulangan dalam tes, jika akan memakan waktu yang sangat lama, yang mengalahkan salah satu tujuan utama pengujian unit: jalankan tes cepat !
CJ Dennis

5

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.

def time_ago(delta, unit):
    delta_str = _number_to_text(delta) + " " + unit;
    if delta == 1:
        return delta_str + " ago"
    else:
        return delta_str = "s ago"

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 time_ago(delta, "day")

if delta < 30:
    weeks = math.floor(delta / 7)
    return time_ago(weeks, "week")

if delta < 365:
    months = math.floor(delta / 31)
    return time_ago(months, "month")

5

Pengembangan yang digerakkan oleh tes tidak membantu.

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:

  • Jangan menulis tes untuk mengonfirmasi fungsi yang sedang diuji berfungsi saat Anda membuatnya. Tulis tes yang sengaja mematahkannya.

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.


1

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:

  • Mengumpulkan persyaratan menyeluruh memungkinkan kita untuk mengetahui seperti apa perilaku yang keliru dalam kode kita.
  • Menulis kode yang bersih, dirancang dengan hati-hati, membuatnya lebih mudah untuk menghindari memperkenalkan bug di tempat pertama dan lebih mudah untuk memperbaikinya ketika kita mengidentifikasi mereka.
  • Tes penulisan memungkinkan kami menghasilkan catatan tentang apa yang kami yakini sebagai bug paling buruk dalam perangkat lunak kami dan membuktikan bahwa kami menghindari setidaknya bug itu. TDD memproduksi tes-tes tersebut sebelum kode, BDD mendapatkan tes-tes tersebut dari persyaratan, dan pengujian unit kuno menghasilkan tes setelah kode ditulis, tetapi semuanya mencegah regresi terburuk di masa depan.
  • Tinjauan rekan berarti bahwa setiap kode waktu diubah, setidaknya dua pasang mata telah melihat kode tersebut, sehingga mengurangi seberapa sering bug masuk ke master.
  • Menggunakan pelacak bug atau pelacak cerita pengguna yang memperlakukan bug sebagai cerita pengguna berarti bahwa ketika bug muncul, mereka melacak dan pada akhirnya ditangani, tidak dilupakan dan dibiarkan secara konsisten menghalangi pengguna.
  • Menggunakan server pementasan berarti bahwa sebelum rilis besar, semua bug show-stopper memiliki kesempatan untuk muncul dan ditangani.
  • Menggunakan kontrol versi berarti bahwa dalam skenario terburuk, di mana kode dengan bug utama dikirim ke pelanggan, Anda dapat melakukan pengembalian darurat dan mendapatkan produk yang dapat diandalkan kembali ke tangan pelanggan Anda saat Anda menyelesaikan masalah.

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.


1

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.


1

(dan percaya bahwa itu ada hubungannya dengan zona waktu, meskipun penggunaan UTC yang sama dalam kode)

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.


0
  • 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".


0

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:

Persyaratannya relatif jelas

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!


0

Sangat mudah untuk melakukan kesalahan logis bahkan dalam kode sumber yang sederhana.

Iya. Pengembangan yang digerakkan oleh tes tidak mengubah hal itu. Anda masih dapat membuat bug dalam kode aktual, dan juga dalam kode pengujian.

Pengembangan yang digerakkan oleh tes tidak membantu.

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.

Yang juga mengkhawatirkan adalah saya tidak bisa melihat bagaimana bug seperti itu bisa dihindari.

Kamu tidak bisa Bahkan NASA belum menemukan cara untuk menghindari bug; kita manusia yang lebih rendah tentu tidak.

Selain berpikir lebih banyak sebelum menulis kode,

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).

satu-satunya cara yang dapat saya pikirkan adalah menambahkan banyak pernyataan untuk kasus-kasus yang saya yakini tidak akan pernah terjadi (seperti yang saya yakini sehari lalu adalah kemarin), dan kemudian mengulang setiap detik selama sepuluh tahun terakhir, memeriksa setiap pelanggaran pernyataan, yang tampaknya terlalu rumit.

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.

Bagaimana saya bisa menghindari membuat bug ini sejak awal?

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.


-2

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.


Ini sangat kekalahan. That in turn means you should spend less time using these techniques- tetapi Anda baru saja mengatakan itu akan membantu dengan lebih sedikit bug ?!
JᴀʏMᴇᴇ

@ JᴀʏMᴇᴇ lebih merupakan sikap pragmatis dari teknik mana yang paling membuat Anda bersemangat. Saya tahu orang-orang yang bangga bahwa mereka menghabiskan 10 kali menulis tes daripada yang mereka lakukan pada kode mereka, dan mereka masih memiliki bug Jadi lebih masuk akal, daripada dogmatis, tentang teknik pengujian sangat penting. Dan tes integrasi harus tetap digunakan, jadi lakukan lebih banyak upaya daripada tes unit.
gbjbaanb
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.