Saya sangat yakin akan nilai menggunakan tes yang memverifikasi program lengkap (misalnya tes konvergensi), termasuk serangkaian uji regresi otomatis . Setelah membaca beberapa buku pemrograman, saya merasa ngeri bahwa saya "harus" menulis unit test (yaitu, tes yang memverifikasi kebenaran fungsi tunggal dan tidak sama dengan menjalankan seluruh kode untuk menyelesaikan masalah) juga . Namun, tes unit tampaknya tidak selalu sesuai dengan kode ilmiah, dan akhirnya terasa seperti buatan atau seperti buang-buang waktu.
Haruskah kita menulis unit test untuk kode penelitian?
programming-paradigms
testing
David Ketcheson
sumber
sumber
Jawaban:
Selama bertahun-tahun saya berada di bawah kesalahpahaman bahwa saya tidak punya cukup waktu untuk menulis unit test untuk kode saya. Ketika saya menulis tes, itu adalah hal-hal berat yang membuat saya berpikir bahwa saya seharusnya menulis unit test ketika saya tahu itu diperlukan.
Kemudian saya mulai menggunakan Test Driven Development dan saya menemukan itu sebagai wahyu yang lengkap. Saya sekarang sangat yakin bahwa saya tidak punya waktu untuk tidak menulis tes unit .
Dalam pengalaman saya, dengan mengembangkan dengan pengujian dalam pikiran Anda berakhir dengan antarmuka yang lebih bersih, kelas & modul yang lebih fokus dan umumnya lebih SOLID , kode diuji.
Setiap kali saya bekerja dengan kode lama yang tidak memiliki tes unit dan harus menguji sesuatu secara manual, saya terus berpikir "ini akan jauh lebih cepat jika kode ini sudah memiliki unit test". Setiap kali saya harus mencoba dan menambahkan fungsionalitas unit test ke kode dengan kopling tinggi, saya terus berpikir "ini akan jauh lebih mudah jika telah ditulis dengan cara de-coupled".
Membandingkan dan membedakan dua stasiun percobaan yang saya dukung. Satu telah ada untuk sementara waktu dan memiliki banyak kode warisan, sedangkan yang lainnya relatif baru.
Saat menambahkan fungsionalitas ke lab lama, sering kali turun ke lab dan menghabiskan waktu berjam-jam bekerja melalui implikasi fungsionalitas yang mereka butuhkan dan bagaimana saya dapat menambahkan fungsionalitas itu tanpa mempengaruhi fungsi lainnya. Kode sama sekali tidak diatur untuk memungkinkan pengujian off-line, jadi hampir semuanya harus dikembangkan on-line. Jika saya mencoba mengembangkan secara off-line maka saya akan berakhir dengan lebih banyak objek tiruan daripada yang masuk akal.
Di lab yang lebih baru, saya biasanya dapat menambahkan fungsionalitas dengan mengembangkannya secara off-line di meja saya, mengejek hanya hal-hal yang segera diperlukan, dan kemudian hanya menghabiskan waktu singkat di lab, menambal masalah yang tersisa tidak diambil. -baris.
Untuk kejelasan, dan karena @ naught101 bertanya ...
Saya cenderung bekerja pada kontrol eksperimental dan perangkat lunak akuisisi data, dengan beberapa analisis data ad hoc, sehingga kombinasi TDD dengan kontrol revisi membantu untuk mendokumentasikan perubahan dalam perangkat keras eksperimen yang mendasarinya dan juga perubahan dalam persyaratan pengumpulan data dari waktu ke waktu.
Bahkan dalam situasi mengembangkan kode eksplorasi, saya dapat melihat manfaat yang signifikan dari asumsi yang dikodifikasikan, bersama dengan kemampuan untuk melihat bagaimana asumsi-asumsi tersebut berkembang dari waktu ke waktu.
sumber
Kode ilmiah cenderung memiliki konstelasi fungsi yang saling terkait lebih sering daripada kode bisnis yang saya kerjakan, biasanya karena struktur matematika dari masalah. Jadi, saya tidak berpikir unit test untuk fungsi individual sangat efektif. Namun, saya pikir ada kelas unit test yang efektif, dan masih sangat berbeda dari tes seluruh program karena mereka menargetkan fungsionalitas tertentu.
Saya hanya menjelaskan secara singkat apa yang saya maksud dengan tes semacam ini. Pengujian regresi mencari perubahan dalam perilaku yang ada (entah bagaimana divalidasi) ketika perubahan dilakukan pada kode. Pengujian unit menjalankan sepotong kode dan memeriksa apakah ia memberikan output yang diinginkan berdasarkan spesifikasi. Mereka tidak jauh berbeda, karena tes regresi asli adalah tes unit karena saya harus menentukan bahwa output itu valid.
Contoh favorit saya dari uji unit numerik adalah menguji tingkat konvergensi implementasi elemen hingga. Ini jelas tidak sederhana, tetapi dibutuhkan solusi yang diketahui untuk PDE, menjalankan beberapa masalah dengan mengurangi ukuran mesh , dan kemudian menyesuaikan norma kesalahan dengan kurva C h r di mana r adalah tingkat konvergensi. Saya melakukan ini untuk masalah Poisson di PETSc menggunakan Python. Saya tidak mencari perbedaan, seperti dalam regresi, tetapi nilai r khusus untuk elemen yang diberikan.h Chr r r
Dua contoh lagi pengujian unit, yang berasal dari PyLith , adalah lokasi titik, yang merupakan fungsi tunggal yang mudah untuk menghasilkan hasil sintetik, dan pembuatan sel kohesif volume nol dalam sebuah mesh, yang melibatkan beberapa fungsi tetapi membahas sepotong terbatas fungsi dalam kode.
Ada banyak tes semacam ini, termasuk tes konservasi dan konsistensi. Operasi tidak jauh berbeda dari regresi (Anda menjalankan tes dan memeriksa output terhadap standar), tetapi output standar berasal dari spesifikasi daripada lari sebelumnya.
sumber
Sejak saya membaca tentang Test-Driven Development di Code Complete, edisi ke-2 , saya telah menggunakan kerangka pengujian unitsebagai bagian dari strategi pengembangan saya, dan itu secara dramatis meningkatkan produktivitas saya dengan mengurangi jumlah waktu yang saya habiskan untuk debugging karena berbagai tes yang saya tulis bersifat diagnostik. Sebagai manfaat sampingan, saya jauh lebih percaya diri dalam hasil ilmiah saya, dan telah menggunakan unit test saya pada sejumlah kesempatan untuk mempertahankan hasil saya. Jika ada kesalahan dalam tes unit, saya biasanya bisa mengetahui mengapa cukup cepat. Jika aplikasi saya macet dan semua unit test saya lulus, saya melakukan analisis cakupan kode untuk melihat bagian mana dari kode saya yang tidak dijalankan, serta melangkah melalui kode dengan debugger untuk menentukan sumber kesalahan. Lalu saya menulis tes baru untuk memastikan bahwa bug tetap diperbaiki.
Banyak tes yang saya tulis bukan tes unit murni. Didefinisikan dengan ketat, unit test seharusnya menjalankan fungsionalitas dari satu fungsi. Ketika saya dapat dengan mudah menguji satu fungsi menggunakan data tiruan, saya melakukannya. Di lain waktu, saya tidak bisa dengan mudah mengejek data yang saya butuhkan untuk menulis tes yang menjalankan fungsi dari fungsi yang diberikan, jadi saya akan menguji fungsi itu bersama dengan orang lain dalam tes integrasi. Tes integrasimenguji perilaku beberapa fungsi sekaligus. Seperti yang Matt tunjukkan, kode ilmiah sering merupakan rasi fungsi yang saling terkait, tetapi sering kali, fungsi tertentu disebut secara berurutan, dan unit test dapat ditulis untuk menguji output pada langkah-langkah menengah. Misalnya, jika kode produksi saya memanggil lima fungsi secara berurutan, saya akan menulis lima tes. Tes pertama akan memanggil fungsi pertama saja (jadi ini adalah unit test). Kemudian tes kedua akan memanggil fungsi pertama dan kedua, tes ketiga akan memanggil tiga fungsi pertama, dan seterusnya. Bahkan jika saya dapat menulis unit test untuk setiap fungsi tunggal dalam kode saya, saya tetap akan menulis tes integrasi, karena bug dapat muncul ketika berbagai potongan modular dari suatu program digabungkan. Akhirnya, setelah menulis semua tes unit dan tes integrasi, saya pikir saya perlu, saya Saya akan membungkus studi kasus saya dalam unit test dan menggunakannya untuk pengujian regresi, karena saya ingin hasil saya dapat diulang. Jika tidak dapat diulang, dan saya mendapatkan hasil yang berbeda, saya ingin tahu mengapa. Kegagalan tes regresi mungkin bukan masalah nyata, tetapi itu akan memaksa saya untuk mencari tahu apakah hasil baru setidaknya sama dapat dipercaya dengan hasil lama.
Juga bermanfaat bersama dengan pengujian unit adalah analisis kode statis, penghilang memori, dan kompilasi dengan flag peringatan kompiler untuk menangkap kesalahan sederhana dan kode yang tidak digunakan.
sumber
Dalam pengalaman saya, ketika kompleksitas kode penelitian ilmiah meningkat, ada kebutuhan untuk memiliki pendekatan yang sangat modular dalam pemrograman. Ini bisa menyakitkan untuk kode dengan basis besar dan kuno (
f77
siapa pun?) Tetapi perlu bergerak maju. Ketika modul dibangun berdasarkan aspek tertentu dari kode (untuk aplikasi CFD, pikirkan Ketentuan Batas atau Termodinamika), pengujian unit sangat berharga untuk memvalidasi implementasi baru dan mengisolasi masalah dan pengembangan perangkat lunak lebih lanjut.Unit test ini harus satu tingkat di bawah verifikasi kode (dapatkah saya memulihkan solusi analitik persamaan gelombang saya?) Dan 2 tingkat di bawah validasi kode (dapatkah saya memprediksi nilai RMS puncak yang benar dalam aliran pipa turbulen saya), hanya memastikan bahwa pemrograman (adakah argumen yang diteruskan dengan benar, apakah pointer menunjuk ke hal yang benar?) dan "matematika" (subrutin ini menghitung koefisien gesekan. Jika saya memasukkan satu set angka dan menghitung solusinya dengan tangan, apakah rutin menghasilkan sama hasil?) benar. Pada dasarnya naik satu tingkat di atas apa yang bisa dilihat oleh kompiler, yaitu kesalahan sintaksis dasar.
Saya pasti akan merekomendasikannya untuk setidaknya beberapa modul penting dalam aplikasi Anda. Namun, kita harus menyadari bahwa ini sangat membosankan dan menghabiskan waktu jadi kecuali Anda memiliki tenaga manusia yang tidak terbatas, saya tidak akan merekomendasikannya untuk 100% dari kode yang kompleks.
sumber
Pengujian unit untuk kode ilmiah berguna karena berbagai alasan.
Tiga khususnya adalah:
Tes unit membantu orang lain memahami batasan kode Anda. Pada dasarnya, unit test adalah bentuk dokumentasi.
Tes unit memeriksa untuk memastikan bahwa satu unit kode mengembalikan hasil yang benar, dan memeriksa untuk memastikan bahwa perilaku suatu program tidak berubah ketika detail dimodifikasi.
Menggunakan unit test memudahkan untuk memodulasi kode penelitian Anda. Ini bisa sangat penting jika Anda mulai mencoba menargetkan kode Anda di platform baru, misalnya Anda tertarik untuk memparalelkannya, atau menjalankannya pada mesin GPGPU.
Yang paling penting, tes unit memberi Anda keyakinan bahwa hasil penelitian yang Anda hasilkan menggunakan kode Anda valid dan dapat diverifikasi.
Saya perhatikan bahwa Anda menyebutkan pengujian regresi dalam pertanyaan Anda. Dalam banyak kasus, pengujian regresi dicapai melalui otomatisasi, pelaksanaan rutin unit test dan / atau tes integrasi (yang menguji bahwa potongan-potongan kode bekerja dengan benar ketika digabungkan; dalam komputasi ilmiah, ini sering dilakukan dengan membandingkan keluaran ke data eksperimental atau hasil dari program sebelumnya yang tepercaya). Sepertinya Anda sudah menggunakan tes integrasi atau unit test pada tingkat komponen kompleks besar dengan sukses.
Apa yang akan saya katakan adalah bahwa ketika kode penelitian semakin kompleks, dan bergantung pada kode dan perpustakaan orang lain, penting untuk memahami di mana kesalahan terjadi ketika itu terjadi. Unit testing memungkinkan kesalahan untuk menunjukkan dengan lebih mudah.
Anda dapat menemukan deskripsi, bukti, dan referensi di Bagian 7 "Rencana kesalahan" dari makalah yang saya tulis bersama tentang Praktik Terbaik untuk Komputasi Ilmiah bermanfaat - ini juga memperkenalkan konsep komplementer pemrograman defensif.
sumber
Di kelas deal.II saya mengajarkan bahwa perangkat lunak yang tidak memiliki tes tidak bekerja dengan benar (dan pergi untuk stres bahwa saya sengaja mengatakan " tidak tidak bekerja dengan benar", bukan " mungkin tidak bekerja dengan benar).
Tentu saja saya hidup dengan mantra - begitulah kesepakatannya. Saya telah menjalankan 2.500 tes dengan setiap komit ;-)
Lebih serius, saya pikir Matt sudah mendefinisikan dua kelas tes dengan baik. Kami menulis unit test untuk hal-hal tingkat yang lebih rendah dan itu semacam berkembang secara alami untuk tes regresi untuk hal-hal tingkat yang lebih tinggi. Saya tidak berpikir saya bisa menggambar batas yang jelas yang akan memisahkan pengujian kami ke satu sisi atau yang lain, pasti ada banyak yang menginjak garis di mana seseorang telah melihat output dan menemukan itu sebagian besar masuk akal (unit test?) tanpa harus melihat sedikit keakuratan terakhir (uji regresi?).
sumber
Iya dan tidak. Tentu saja tidak cocok untuk rutinitas mendasar dari perangkat dasar yang Anda gunakan untuk membuat hidup Anda lebih mudah, seperti rutinitas konversi, pemetaan string, fisika dasar dan matematika, dll. Ketika datang ke kelas perhitungan atau fungsi, mereka biasanya membutuhkan waktu jangka panjang, dan Anda mungkin lebih suka mengujinya sebagai tes fungsional, bukan sebagai unit. Juga, unittest dan sangat menekankan kelas dan entitas yang tingkat dan penggunaannya akan banyak berubah (misalnya untuk tujuan optimasi) atau yang detail internalnya akan diubah karena alasan apa pun. Contoh paling khas adalah kelas yang membungkus matriks besar, dipetakan dari disk.
sumber
Benar!
Apa, itu tidak cukup untukmu?
Dalam pemrograman ilmiah lebih dari jenis apa pun, kami sedang mengembangkan berdasarkan upaya mencocokkan sistem fisik. Bagaimana Anda tahu jika Anda telah melakukan itu selain dari pengujian? Bahkan sebelum Anda mulai membuat kode, putuskan bagaimana Anda akan menggunakan kode Anda dan buat beberapa contoh proses. Cobalah untuk menangkap setiap kasus tepi yang mungkin. Lakukan dengan cara modular - misalnya, untuk jaringan saraf Anda dapat membuat satu set tes untuk satu neuron dan satu set tes untuk jaringan saraf lengkap. Dengan begitu ketika Anda mulai menulis kode Anda dapat memastikan neuron Anda berfungsi sebelum Anda mulai bekerja di jaringan. Bekerja dalam tahap seperti ini berarti bahwa ketika Anda mengalami masalah Anda hanya memiliki 'tahap' kode yang paling baru untuk diuji, tahap sebelumnya telah diuji.
Plus, setelah Anda memiliki tes, jika Anda perlu menulis ulang kode dalam bahasa yang berbeda (misalnya, mengkonversi ke CUDA), atau bahkan jika Anda baru saja memperbarui, Anda sudah memiliki testcases dan Anda dapat menggunakannya untuk membuat Pastikan kedua versi program Anda bekerja dengan cara yang sama.
sumber
Iya.
Gagasan bahwa kode apa pun ditulis tanpa unit test adalah laknat. Kecuali Anda membuktikan kode Anda benar dan kemudian membuktikan buktinya benar = P.
sumber
Saya akan mendekati pertanyaan ini secara pragmatis daripada dogmatis. Tanyakan kepada diri Anda pertanyaan: "Apa yang bisa salah dalam fungsi X?" Bayangkan apa yang terjadi pada output ketika Anda memasukkan beberapa bug khas ke dalam kode: prefactor yang salah, indeks yang salah, ... Dan kemudian tulis unit test yang mungkin mendeteksi bug semacam itu. Jika untuk fungsi yang diberikan tidak ada cara untuk menulis tes seperti itu tanpa mengulang kode fungsi itu sendiri, maka jangan - tetapi pikirkan tentang tes pada tingkat yang lebih tinggi berikutnya.
Masalah yang jauh lebih penting dengan tes unit (atau bahkan tes apa pun ) dalam kode ilmiah adalah bagaimana menghadapi ketidakpastian aritmatika floating-point. Sejauh yang saya tahu belum ada solusi umum yang baik.
sumber
Saya merasa kasihan pada Tangurena - di sekitar sini, mantra adalah "Kode yang belum diuji adalah kode yang rusak" dan itu datang dari bos. Daripada mengulangi semua alasan bagus untuk melakukan pengujian unit, saya hanya ingin menambahkan beberapa spesifik.
sumber
Saya telah menggunakan pengujian unit untuk efek yang baik pada beberapa kode skala kecil (yaitu programmer tunggal), termasuk versi ketiga dari kode analisis disertasi saya dalam fisika partikel.
Dua versi pertama telah runtuh karena beratnya sendiri dan multiplikasi interkoneksi.
Yang lain telah menulis bahwa interaksi antar modul seringkali merupakan tempat di mana koding ilmiah terputus, dan mereka benar tentang hal itu. Tapi itu jauh lebih mudah untuk mendiagnosa orang masalah ketika Anda dapat menunjukkan secara meyakinkan bahwa setiap modul adalah melakukan apa yang dimaksudkan untuk melakukan.
sumber
Pendekatan yang sedikit berbeda yang saya gunakan ketika mengembangkan pemecah kimia (untuk domain geologi yang kompleks) adalah apa yang Anda sebut Unit Testing oleh Copy and Paste Snippet .
Membangun test harness untuk kode asli yang tertanam dalam modeller sistem kimia besar tidak layak dalam jangka waktu.
Namun, saya dapat mengerjakan set potongan yang semakin kompleks yang menunjukkan bagaimana pengurai (Boost Spirit) untuk formula kimia bekerja, sebagai unit test untuk ekspresi yang berbeda.
Tes unit terakhir, paling kompleks sangat dekat dengan kode yang diperlukan dalam sistem, tanpa harus mengubah kode itu agar dapat dipermainkan. Dengan demikian saya dapat menyalin seluruh kode unit yang saya uji.
Apa yang menjadikan ini lebih dari sekadar latihan pembelajaran dan rangkaian regresi sejati adalah dua faktor - unit test disimpan di sumber utama dan dijalankan sebagai bagian dari tes lain untuk aplikasi itu (dan ya, mereka memang mengambil efek samping dari Boost Semangat berubah 2 tahun kemudian) - karena kode yang disalin dan disisipkan secara minimal dimodifikasi dalam aplikasi nyata, itu bisa memiliki komentar merujuk kembali ke unit test untuk membantu seseorang tetap selaras.
sumber
Untuk basis kode yang lebih besar, tes (tidak harus unit test) untuk hal-hal tingkat tinggi berguna. Tes unit untuk beberapa algoritma sederhana juga berguna untuk memastikan kode Anda tidak masuk akal karena fungsi pembantu Anda menggunakan
sin
alih-alihcos
.Tetapi untuk keseluruhan kode penelitian sangat sulit untuk menulis dan mempertahankan tes. Algoritma cenderung besar tanpa hasil antara yang berarti yang dapat memiliki tes yang jelas dan sering membutuhkan waktu lama untuk dijalankan sebelum ada hasilnya. Tentu saja Anda dapat menguji terhadap referensi berjalan yang memiliki hasil bagus, tetapi ini bukan tes yang baik dalam arti unit test.
Hasil seringkali merupakan perkiraan solusi yang sebenarnya. Meskipun Anda dapat menguji fungsi-fungsi sederhana Anda jika akurat hingga beberapa epsilon, akan sangat sulit untuk memverifikasi apakah beberapa hasil mesh benar atau tidak, yang dievaluasi dengan inspeksi visual oleh pengguna (Anda) sebelumnya.
Dalam kasus seperti itu, tes otomatis seringkali memiliki rasio biaya / manfaat yang terlalu tinggi. Saya merekomendasikan sesuatu yang lebih baik: Menulis program pengujian. Sebagai contoh saya menulis skrip python ukuran sedang untuk membuat data tentang hasil, seperti histogram ukuran tepi dan sudut mesh, area segitiga terbesar dan terkecil serta perbandingannya, dll.
Saya dapat menggunakannya untuk mengevaluasi jerat input dan output selama operasi normal dan menggunakannya untuk melakukan pemeriksaan kewarasan setelah saya mengubah algoritme. Ketika saya mengubah algoritme, saya tidak selalu tahu apakah hasil yang baru lebih baik, karena seringkali tidak ada ukuran absolut yang merupakan perkiraan yang terbaik. Tetapi dengan menghasilkan metrik seperti itu, saya dapat mengetahui beberapa faktor apa yang lebih baik seperti "Varian baru pada akhirnya memiliki rasio sudut yang lebih baik tetapi tingkat konvergensi yang lebih buruk".
sumber