Bagaimana Anda menulis unit test untuk kode dengan hasil yang sulit diprediksi?

124

Saya sering bekerja dengan program yang sangat numerik / matematis, di mana hasil pasti dari suatu fungsi sulit diprediksi sebelumnya.

Dalam mencoba menerapkan TDD dengan kode semacam ini, saya sering menemukan bahwa menulis kode yang sedang diuji secara signifikan lebih mudah daripada menulis unit test untuk kode itu, karena satu-satunya cara saya tahu untuk menemukan hasil yang diharapkan adalah menerapkan algoritma itu sendiri (baik dalam kepala, di atas kertas, atau oleh komputer). Ini terasa salah, karena saya secara efektif menggunakan kode yang sedang diuji untuk memverifikasi unit test saya, bukan sebaliknya.

Adakah teknik yang dikenal untuk menulis unit test dan menerapkan TDD ketika hasil kode yang diuji sulit diprediksi?

Contoh kode (nyata) dengan hasil yang sulit diprediksi:

Sebuah fungsi weightedTasksOnTimeyang, diberikan sejumlah pekerjaan yang dilakukan per hari workPerDaydalam kisaran (0, 24], waktu saat ini initialTime> 0, dan daftar tugas taskArray; masing-masing dengan waktu untuk menyelesaikan properti time> 0, tanggal jatuh tempo due, dan nilai penting importance; pengembalian nilai yang dinormalisasi dalam rentang [0, 1] mewakili pentingnya tugas yang dapat diselesaikan sebelum duetanggal mereka jika setiap tugas diselesaikan dalam urutan yang diberikan oleh taskArray, mulai dari initialTime.

Algoritme untuk mengimplementasikan fungsi ini relatif mudah: beralih pada tugas di taskArray. Untuk setiap tugas, tambahkan timeke initialTime. Jika waktu baru < due, tambahkan importanceke akumulator. Waktu disesuaikan dengan workPerDay terbalik. Sebelum mengembalikan akumulator, bagi dengan jumlah kepentingan tugas untuk dinormalisasi.

function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time * (24 / workPerDay)
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator / totalImportance(taskArray)
}

Saya percaya masalah di atas dapat disederhanakan, dengan tetap mempertahankan intinya, dengan menghapus workPerDaydan persyaratan normalisasi, untuk memberikan:

function weightedTasksOnTime(initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator
}

Pertanyaan ini membahas situasi di mana kode yang diuji bukan implementasi ulang dari algoritma yang ada. Jika kode adalah implementasi ulang, secara intrinsik memiliki mudah untuk memprediksi hasil, karena implementasi algoritma yang dipercaya bertindak sebagai ramalan uji alami.

LukisanInAir
sumber
4
Bisakah Anda memberikan contoh sederhana dari fungsi yang hasilnya sulit diprediksi?
Robert Harvey
62
FWIW Anda tidak menguji algoritme. Agaknya itu benar. Anda sedang menguji implementasi. Berolahraga dengan tangan sering kali baik sebagai konstruksi paralel.
Kristian H
7
Ada situasi di mana suatu algoritma tidak dapat diuji unit secara masuk akal - misalnya jika waktu pelaksanaannya beberapa hari / bulan. Ini dapat terjadi ketika memecahkan Masalah NP. Dalam kasus ini, mungkin lebih layak untuk memberikan bukti formal bahwa kode itu benar.
Hulk
12
Sesuatu yang saya lihat dalam kode numerik yang sangat rumit adalah memperlakukan unit test hanya sebagai tes regresi. Tulis fungsi, jalankan untuk beberapa nilai menarik, validasi hasil secara manual, kemudian tulis unit test untuk menangkap regresi dari hasil yang diharapkan. Coding horor? Ingin tahu apa yang orang lain pikirkan.
Chuu

Jawaban:

251

Ada dua hal yang dapat Anda uji dalam kode yang sulit untuk diuji. Pertama, kasus-kasus yang merosot. Apa yang terjadi jika Anda tidak memiliki elemen dalam larik tugas Anda, atau hanya satu, atau dua tetapi satu melewati batas waktu, dll. Apa pun yang lebih sederhana daripada masalah Anda yang sebenarnya, tetapi masih masuk akal untuk menghitung secara manual.

Yang kedua adalah cek kewarasan. Ini adalah cek yang Anda lakukan di mana Anda tidak tahu apakah jawaban itu benar , tetapi Anda pasti tahu jika itu salah . Ini adalah hal-hal seperti waktu harus bergerak maju, nilai harus dalam kisaran yang masuk akal, persentase harus ditambah hingga 100, dll.

Ya, ini tidak sebagus tes penuh, tetapi Anda akan terkejut betapa sering Anda mengacaukan pemeriksaan kewarasan dan menurunkan kasus, yang mengungkapkan masalah dalam algoritma lengkap Anda.

Karl Bielefeldt
sumber
54
Pikir ini saran yang sangat bagus. Mulailah dengan menulis tes unit semacam ini. Ketika Anda mengembangkan perangkat lunak, jika Anda menemukan bug atau jawaban yang salah - tambahkan itu sebagai unit test. Lakukan hal yang sama, sampai batas tertentu, ketika Anda menemukan jawaban yang pasti benar. Bangun mereka dari waktu ke waktu, dan Anda (pada akhirnya) akan memiliki serangkaian unit test yang sangat lengkap meskipun mulai tidak tahu apa yang akan mereka lakukan ...
Algy Taylor
21
Hal lain yang mungkin bisa membantu dalam beberapa kasus (walaupun mungkin bukan yang ini) adalah menulis fungsi terbalik dan menguji bahwa, ketika dirantai, input dan output Anda sama.
Cyberspark
7
cek kewarasan sering membuat target yang baik untuk tes berbasis properti dengan sesuatu seperti QuickCheck
jk.
10
satu kategori tes lainnya yang saya rekomendasikan adalah beberapa untuk memeriksa perubahan output yang tidak disengaja. Anda dapat 'menipu' ini dengan menggunakan kode itu sendiri untuk menghasilkan hasil yang diharapkan karena maksud dari ini adalah untuk membantu pengelola dengan memberi tanda bahwa sesuatu yang dimaksudkan sebagai perubahan netral keluaran tidak sengaja memengaruhi perilaku algoritmik.
Dan Neely
5
@ iFlo Tidak yakin apakah Anda bercanda, tetapi kebalikannya sudah ada. Layak menyadari bahwa tes gagal mungkin menjadi masalah dalam fungsi terbalik
lucidbrot
80

Saya biasa menulis tes untuk perangkat lunak ilmiah dengan output yang sulit diprediksi. Kami banyak menggunakan Hubungan Metamorf. Pada dasarnya ada hal-hal yang Anda ketahui tentang bagaimana perangkat lunak Anda harus bersikap bahkan jika Anda tidak tahu output numerik yang tepat.

Contoh yang mungkin untuk kasus Anda: jika Anda mengurangi jumlah pekerjaan yang dapat Anda lakukan setiap hari, maka jumlah total pekerjaan yang dapat Anda lakukan akan tetap sama, tetapi kemungkinan menurun. Jadi jalankan fungsi untuk sejumlah nilai workPerDaydan pastikan relasi tersebut berlaku.

James Elderfield
sumber
32
Hubungan metamorf contoh spesifik pengujian berbasis properti , yang secara umum merupakan alat yang berguna untuk situasi seperti ini
Dannnno
38

Jawaban lain memiliki ide bagus untuk mengembangkan tes untuk kasus tepi atau kesalahan. Bagi yang lain, menggunakan algoritma itu sendiri tidak ideal (jelas) tetapi masih bermanfaat.

Ini akan mendeteksi jika algoritma (atau data tergantung pada) telah berubah

Jika perubahan itu merupakan kecelakaan, Anda dapat memutar kembali komit. Jika perubahan itu disengaja, Anda perlu mengunjungi kembali unit test.

pengguna949300
sumber
6
Sebagai catatan, tes semacam ini sering disebut "tes regresi" sesuai tujuannya, dan pada dasarnya adalah jaring pengaman untuk setiap modifikasi / refactoring.
Pac0
21

Cara yang sama Anda menulis unit test untuk jenis kode lain:

  1. Temukan beberapa test case yang representatif, dan ujilah.
  2. Temukan casing tepi, dan uji itu.
  3. Temukan kondisi kesalahan, dan uji itu.

Kecuali jika kode Anda melibatkan beberapa elemen acak atau tidak deterministik (artinya tidak akan menghasilkan output yang sama dengan input yang sama), itu dapat diuji unit.

Hindari efek samping, atau fungsi yang dipengaruhi oleh kekuatan luar. Fungsi murni lebih mudah untuk diuji.

Robert Harvey
sumber
2
Untuk algoritme non-deterministik, Anda dapat menyimpan seed RNG atau mengejeknya menggunakan deret tetap atau deret determinitistik perbedaan rendah, misalnya deret Halton
ajaib
14
@PaintingInAir Jika tidak mungkin untuk memverifikasi output algoritma, dapatkah algoritma itu salah?
WolfgangGroiss
5
Unless your code involves some random elementKuncinya di sini adalah untuk membuat generator angka acak Anda menjadi ketergantungan yang disuntikkan, sehingga Anda dapat menggantinya dengan generator angka yang memberikan hasil persis seperti yang Anda inginkan. Ini memungkinkan Anda untuk menguji lagi secara akurat - menghitung angka yang dihasilkan sebagai parameter input juga. not deterministic (i.e. it won't produce the same output given the same input)Karena tes unit harus dimulai dari situasi yang terkendali , itu hanya dapat bersifat non-deterministik jika memiliki elemen acak - yang kemudian dapat Anda injeksi. Saya tidak bisa memikirkan kemungkinan lain di sini.
Flater
3
@PaintingInAir: Entah atau. Komentar saya berlaku untuk eksekusi cepat atau penulisan tes cepat. Jika Anda membutuhkan tiga hari untuk menghitung satu contoh dengan tangan (misalkan Anda menggunakan metode tercepat yang tersedia yang tidak menggunakan kode) - maka tiga hari adalah waktu yang diperlukan. Jika Anda sebagai gantinya mendasarkan hasil tes yang diharapkan pada kode aktual itu sendiri, maka tes itu mengkompromikan dirinya sendiri. Itu seperti melakukan if(x == x), itu adalah perbandingan yang tidak berguna. Anda memerlukan dua hasil Anda ( aktual : berasal dari kode; diharapkan : berasal dari pengetahuan eksternal Anda) agar tidak tergantung satu sama lain.
Flater
2
Ini masih dapat diuji unit meskipun tidak deterministik, asalkan memenuhi spesifikasi dan kepatuhan dapat diukur (mis. Distribusi dan sebaran secara acak) Mungkin hanya memerlukan banyak sampel untuk menghilangkan risiko anomali.
mckenzm
17

Pembaruan karena komentar yang diposting

Jawaban asli dihapus demi singkatnya - Anda dapat menemukannya di riwayat edit.

PaintingInAir Untuk konteks: sebagai pengusaha dan akademis, sebagian besar algoritma yang saya desain tidak diminta oleh orang lain selain saya. Contoh yang diberikan dalam pertanyaan adalah bagian dari pengoptimal bebas derivatif untuk memaksimalkan kualitas pemesanan tugas. Dalam hal bagaimana saya menggambarkan perlunya fungsi contoh secara internal: "Saya membutuhkan fungsi objektif untuk memaksimalkan pentingnya tugas yang selesai tepat waktu". Namun, tampaknya masih ada kesenjangan besar antara permintaan ini dan pelaksanaan tes unit.

Pertama, TL; DR untuk menghindari jawaban yang panjang:

Pikirkan seperti ini:
Seorang pelanggan memasuki McDonald's, dan meminta burger dengan selada, tomat, dan sabun tangan sebagai topping. Pesanan ini diberikan kepada juru masak, yang membuat burger persis seperti yang diminta. Pelanggan menerima burger ini, memakannya, dan kemudian mengeluh kepada juru masak bahwa ini bukan burger yang enak!

Ini bukan kesalahan koki - dia hanya melakukan apa yang diminta pelanggan secara eksplisit. Bukan tugas koki untuk memeriksa apakah pesanan yang diminta benar-benar enak . Si juru masak hanya menciptakan apa yang dipesan pelanggan. Adalah tanggung jawab pelanggan untuk memesan sesuatu yang mereka rasa enak .

Demikian pula, itu bukan tugas pengembang untuk mempertanyakan kebenaran dari algoritma. Satu-satunya tugas mereka adalah mengimplementasikan algoritma seperti yang diminta.
Pengujian unit adalah alat pengembang. Ini mengkonfirmasi bahwa burger cocok dengan pesanan (sebelum meninggalkan dapur). Itu tidak (dan tidak boleh) mencoba untuk mengkonfirmasi bahwa burger yang dipesan sebenarnya enak.

Meskipun Anda adalah pelanggan dan juru masak, masih ada perbedaan yang berarti antara:

  • Saya tidak menyiapkan makanan ini dengan benar, itu tidak enak (= kesalahan memasak). Steak yang dibakar tidak akan pernah terasa enak, bahkan jika Anda suka steak.
  • Saya menyiapkan makanan dengan benar, tetapi saya tidak menyukainya (= kesalahan pelanggan). Jika Anda tidak suka steak, Anda tidak akan pernah suka makan steak, bahkan jika Anda memasaknya dengan sempurna.

Masalah utama di sini adalah bahwa Anda tidak membuat pemisahan antara pelanggan dan pengembang (dan analis - meskipun peran itu dapat diwakili oleh pengembang juga).

Anda perlu membedakan antara menguji kode, dan menguji persyaratan bisnis.

Sebagai contoh, pelanggan menginginkannya bekerja seperti ini . Namun, pengembang salah paham, dan dia menulis kode yang melakukan itu .

Oleh karena itu pengembang akan menulis unit test yang menguji apakah [itu] berfungsi seperti yang diharapkan. Jika ia mengembangkan aplikasi dengan benar, pengujian unitnya akan berlalu meskipun aplikasi tidak melakukan [ini] , yang diharapkan pelanggan.

Jika Anda ingin menguji harapan pelanggan (persyaratan bisnis), itu perlu dilakukan dalam langkah terpisah (dan kemudian).

Alur kerja pengembangan sederhana untuk menunjukkan kepada Anda kapan tes ini harus dijalankan:

  • Pelanggan menjelaskan masalah yang ingin mereka selesaikan.
  • Analis (atau pengembang) menuliskan ini dalam analisis.
  • Pengembang menulis kode yang melakukan apa yang dijelaskan analisis.
  • Pengembang menguji kodenya (unit test) untuk melihat apakah ia mengikuti analisis dengan benar
  • Jika unit gagal, pengembang akan kembali mengembangkan. Loop ini tanpa batas, sampai unit tes semua berlalu.
  • Sekarang memiliki basis kode yang teruji (dikonfirmasi dan lulus), pengembang membuat aplikasi.
  • Aplikasi ini diberikan kepada pelanggan.
  • Pelanggan sekarang menguji apakah aplikasi yang diberikan kepadanya benar-benar memecahkan masalah yang dia cari untuk pecahkan (tes QA) .

Anda mungkin bertanya-tanya apa gunanya melakukan dua tes terpisah ketika pelanggan dan pengembang adalah satu dan sama. Karena tidak ada "hand off" dari pengembang ke pelanggan, tes dijalankan satu demi satu, tetapi mereka masih langkah terpisah.

  • Tes unit adalah alat khusus yang membantu Anda memverifikasi apakah tahap pengembangan Anda selesai.
  • Tes QA dilakukan dengan menggunakan aplikasi .

Jika Anda ingin menguji apakah algoritme Anda sendiri benar, itu bukan bagian dari pekerjaan pengembang . Itu adalah keprihatinan pelanggan, dan pelanggan akan menguji ini dengan menggunakan aplikasi.

Sebagai wirausaha dan akademis, Anda mungkin kehilangan perbedaan penting di sini, yang menyoroti tanggung jawab yang berbeda.

  • Jika aplikasi tidak mematuhi apa yang awalnya diminta pelanggan, maka perubahan kode selanjutnya biasanya dilakukan secara gratis ; karena ini adalah kesalahan pengembang. Pengembang melakukan kesalahan dan harus membayar biaya perbaikannya.
  • Jika aplikasi melakukan apa yang awalnya diminta pelanggan, tetapi pelanggan sekarang telah berubah pikiran (misalnya Anda telah memutuskan untuk menggunakan algoritma yang berbeda dan lebih baik), perubahan pada basis kode dibebankan kepada pelanggan , karena itu bukan kesalahan pengembang bahwa pelanggan meminta sesuatu yang berbeda dari apa yang mereka inginkan sekarang. Adalah tanggung jawab pelanggan (biaya) untuk berubah pikiran dan oleh karena itu para pengembang mengeluarkan lebih banyak upaya untuk mengembangkan sesuatu yang sebelumnya tidak disetujui.
Flater
sumber
Saya akan senang melihat elaborasi lebih lanjut tentang situasi "Jika Anda datang dengan algoritma sendiri", karena saya pikir ini adalah situasi yang paling mungkin menimbulkan masalah. Terutama dalam situasi di mana tidak ada contoh "jika A maka B, selain C" disediakan. (ps Saya bukan downvoter)
PaintingInAir
@PaintingInAir: Tapi saya tidak bisa menguraikan ini karena tergantung pada situasi Anda. Jika Anda memutuskan untuk membuat algoritma ini, Anda jelas melakukannya untuk menyediakan fitur tertentu. Siapa yang meminta Anda melakukannya? Bagaimana mereka menggambarkan permintaan mereka? Apakah mereka memberi tahu Anda apa yang mereka butuhkan untuk terjadi dalam skenario tertentu? (informasi ini adalah apa yang saya sebut sebagai "analisis" dalam jawaban saya) Apa pun penjelasan yang Anda terima (yang membuat Anda membuat algoritma) dapat digunakan untuk menguji apakah algoritme berfungsi seperti yang diminta. Singkatnya, apa pun selain kode / algoritma yang dibuat sendiri dapat digunakan.
Flater
2
@PaintingInAir: Berbahaya jika menyandingkan erat pelanggan, analis, dan pengembang; karena Anda cenderung melewatkan langkah-langkah penting seperti mendefinisikan permulaan masalah . Saya yakin itulah yang Anda lakukan di sini. Anda tampaknya ingin menguji kebenaran algoritme, alih-alih apakah diterapkan dengan benar. Tetapi bukan itu yang Anda lakukan. Pengujian implementasi dapat dilakukan menggunakan unit test. Menguji algoritme itu sendiri adalah masalah menggunakan aplikasi Anda (yang diuji) dan memeriksa fakta hasilnya - tes yang sebenarnya ini berada di luar jangkauan basis kode Anda (sebagaimana mestinya ).
Flater
4
Jawaban ini sudah luar biasa. Sangat merekomendasikan mencoba menemukan cara untuk merumuskan kembali konten asli sehingga Anda dapat mengintegrasikannya ke dalam jawaban baru jika Anda tidak ingin membuangnya.
jpmc26
7
Juga, saya tidak setuju dengan premis Anda. Pengujian dapat dan benar-benar harus mengungkapkan ketika kode menghasilkan keluaran yang salah sesuai dengan spesifikasi. Ini valid untuk pengujian untuk memvalidasi output untuk beberapa kasus uji yang diketahui. Juga, juru masak harus tahu lebih baik daripada menerima "sabun tangan" sebagai bahan burger yang valid, dan majikan hampir pasti telah mendidik juru masak tentang bahan apa yang tersedia.
jpmc26
9

Pengujian Properti

Kadang-kadang fungsi matematika lebih baik dilayani oleh "Pengujian Properti" daripada oleh pengujian unit berbasis contoh tradisional. Misalnya, bayangkan Anda sedang menulis unit test untuk sesuatu seperti fungsi integer "multiply". Sementara fungsi itu sendiri mungkin tampak sangat sederhana, jika itu satu-satunya cara untuk memperbanyak, bagaimana Anda mengujinya secara menyeluruh tanpa logika dalam fungsi itu sendiri? Anda bisa menggunakan tabel raksasa dengan input / output yang diharapkan, tetapi ini terbatas dan rawan kesalahan.

Dalam kasus ini, Anda dapat menguji properti fungsi yang diketahui, alih-alih mencari hasil yang diharapkan spesifik. Untuk penggandaan, Anda mungkin tahu bahwa mengalikan angka negatif dan angka positif harus menghasilkan angka negatif, dan bahwa mengalikan dua angka negatif harus menghasilkan angka positif, dll. Menggunakan nilai acak dan kemudian memeriksa bahwa properti ini dipertahankan untuk semua nilai tes adalah cara yang baik untuk menguji fungsi-fungsi tersebut. Anda biasanya perlu menguji lebih dari satu properti, tetapi Anda sering dapat mengidentifikasi serangkaian properti terbatas yang bersama-sama memvalidasi perilaku yang benar dari suatu fungsi tanpa harus mengetahui hasil yang diharapkan untuk setiap kasus.

Salah satu pengantar terbaik untuk Pengujian Properti yang pernah saya lihat adalah yang ini di F #. Semoga sintaksisnya tidak menjadi halangan untuk memahami penjelasan teknik tersebut.

Aaron M. Eshbach
sumber
1
Saya akan menyarankan mungkin menambahkan sesuatu yang sedikit lebih spesifik dalam contoh Anda kembali perkalian, seperti menghasilkan kuartet acak (a, b, c) dan mengonfirmasi bahwa (ab) (cd) menghasilkan (ac-ad) - (bc-bd). Operasi penggandaan bisa sangat rusak dan masih menegakkan aturan (kali negatif negatif menghasilkan positif), tetapi aturan distributif memprediksi hasil tertentu.
supercat
4

Sangat menggoda untuk menulis kode dan kemudian melihat apakah hasilnya "terlihat benar", tetapi, karena Anda memiliki intuisi yang benar, itu bukan ide yang baik.

Ketika algoritma sulit, Anda dapat melakukan sejumlah hal untuk membuat perhitungan manual hasilnya lebih mudah.

  1. Gunakan Excel Siapkan spreadsheet yang melakukan sebagian atau semua perhitungan untuk Anda. Jaga agar tetap sederhana sehingga Anda dapat melihat langkah-langkahnya.

  2. Bagi metode Anda menjadi metode yang lebih kecil yang dapat diuji, masing-masing dengan tes mereka sendiri. Saat Anda yakin bagian yang lebih kecil berfungsi, gunakan untuk mengerjakan langkah selanjutnya secara manual.

  3. Gunakan properti agregat untuk memeriksa kewarasan. Misalnya, Anda memiliki kalkulator probabilitas; Anda mungkin tidak tahu seperti apa hasil masing-masing, tetapi Anda tahu mereka semua harus menambahkan hingga 100%.

  4. Kasar. Tulis program yang menghasilkan semua hasil yang mungkin, dan periksa tidak ada yang lebih baik dari yang dihasilkan oleh algoritma Anda.

Ewan
sumber
Untuk 3., perkenankan beberapa kesalahan pembulatan di sini. Ada kemungkinan bahwa jumlah total Anda hingga 100,000001% atau angka yang hampir sama namun tidak tepat.
Flater
2
Saya tidak begitu yakin tentang 4. Jika Anda dapat menghasilkan hasil yang optimal untuk semua kombinasi input yang mungkin (yang kemudian Anda gunakan untuk konfirmasi pengujian), maka Anda secara inheren sudah mampu menghitung hasil yang optimal dan oleh karena itu jangan ' Anda tidak perlu kode kedua ini yang ingin Anda uji. Pada titik itu, Anda akan lebih baik menggunakan generator hasil optimal yang ada karena sudah terbukti bekerja. (dan jika itu belum terbukti berhasil, maka Anda tidak dapat mengandalkan hasilnya untuk memeriksa faktanya sejak awal).
Flater
6
@ flat biasanya Anda memiliki persyaratan lain serta kebenaran yang tidak dipenuhi oleh brute force. misalnya kinerja.
Ewan
1
@flater Saya benci menggunakan jenis, jalur terpendek, mesin catur, dll jika Anda percaya itu. Tapi saya benar-benar bertaruh dalam kesalahan pembulatan Anda, memungkinkan kasino sepanjang hari
Ewan
3
@flater, apakah Anda mengundurkan diri saat mencapai gim akhir raja? hanya karena seluruh permainan tidak bisa dipaksa paksa tidak berarti posisi indiviual tidak bisa. Hanya karena Anda memaksakan jalur terpendek yang benar ke satu jaringan, tidak berarti Anda mengetahui jalur terpendek di semua jaringan
Ewan
2

TL; DR

Buka bagian "pengujian komparatif" untuk mendapatkan saran yang tidak ada dalam jawaban lain.


Awal

Mulailah dengan menguji kasus-kasus yang harus ditolak oleh algoritma (nol atau negatif workPerDay, misalnya) dan kasus-kasus yang sepele (misalnya tasksarray kosong ).

Setelah itu, Anda ingin menguji kasus yang paling sederhana terlebih dahulu. Untuk tasksinput, kita perlu menguji panjang yang berbeda; harus cukup untuk menguji elemen 0, 1 dan 2 (2 termasuk dalam kategori "banyak" untuk tes ini).

Jika Anda dapat menemukan input yang dapat dihitung secara mental, itu awal yang baik. Suatu teknik yang kadang-kadang saya gunakan adalah mulai dari hasil yang diinginkan dan bekerja kembali (dalam spesifikasi) ke input yang seharusnya menghasilkan hasil itu.

Pengujian komparatif

Kadang-kadang hubungan output ke input tidak jelas, tetapi Anda memiliki hubungan yang dapat diprediksi antara output yang berbeda ketika satu input diubah. Jika saya telah memahami contoh dengan benar, maka menambahkan tugas (tanpa mengubah input lain) tidak akan pernah meningkatkan proporsi pekerjaan yang dilakukan tepat waktu, sehingga kami dapat membuat tes yang memanggil fungsi dua kali - sekali dengan dan sekali tanpa tugas tambahan - dan menegaskan ketimpangan antara dua hasil.

Kekalahan

Kadang-kadang saya harus menggunakan komentar panjang yang menunjukkan hasil yang dihitung dengan tangan dalam langkah-langkah yang sesuai dengan spesifikasi (komentar seperti itu biasanya lebih panjang daripada test case). Kasus terburuk adalah ketika Anda harus mempertahankan kompatibilitas dengan implementasi sebelumnya dalam bahasa yang berbeda atau untuk lingkungan yang berbeda. Terkadang Anda hanya perlu memberi label data pengujian dengan sesuatu seperti /* derived from v2.6 implementation on ARM system */. Itu tidak terlalu memuaskan, tetapi dapat diterima sebagai uji kesetiaan saat porting, atau sebagai penopang jangka pendek.

Pengingat

Atribut yang paling penting dari tes adalah keterbacaannya - jika input dan outputnya buram bagi pembaca, maka tes tersebut memiliki nilai yang sangat rendah, tetapi jika pembaca dibantu untuk memahami hubungan di antara mereka, maka tes tersebut melayani dua tujuan.

Jangan lupa untuk menggunakan "kira-kira sama" untuk hasil yang tidak tepat (misalnya titik-mengambang).

Hindari pengujian berlebihan - hanya tambahkan tes jika mencakup sesuatu (seperti nilai batas) yang tidak dicapai oleh tes lain.

Toby Speight
sumber
2

Tidak ada yang sangat istimewa tentang fungsi sulit untuk diuji ini. Hal yang sama berlaku untuk kode yang menggunakan antarmuka eksternal (katakanlah, REST API dari aplikasi pihak ke-3 yang tidak di bawah kendali Anda dan tentu saja tidak dapat diuji oleh suite pengujian Anda, atau menggunakan perpustakaan pihak ke-3 di mana Anda tidak yakin dengan format byte yang tepat dari nilai pengembalian).

Ini adalah pendekatan yang cukup valid untuk menjalankan algoritma Anda untuk beberapa input yang masuk akal, melihat apa yang dilakukannya, memastikan bahwa hasilnya benar, dan merangkum input dan hasilnya sebagai test case. Anda dapat melakukan ini untuk beberapa kasus dan karenanya mendapatkan beberapa sampel. Cobalah untuk membuat parameter input berbeda mungkin. Dalam kasus panggilan API eksternal, Anda akan melakukan beberapa panggilan terhadap sistem nyata, melacaknya dengan beberapa alat, dan kemudian mengejeknya ke dalam unit test Anda untuk melihat bagaimana program Anda bereaksi - yang sama dengan hanya mengambil beberapa menjalankan kode perencanaan tugas Anda, memverifikasinya dengan tangan, dan kemudian meng-hardcoding hasilnya dalam pengujian Anda.

Kemudian, jelas, bawa kasus tepi seperti (dalam contoh Anda) daftar tugas kosong; hal-hal seperti itu.

Test suite Anda mungkin tidak akan sebagus metode di mana Anda dapat dengan mudah memprediksi hasil; tetapi masih 100% lebih baik daripada tidak ada test suite (atau hanya tes asap).

Jika masalah Anda, meskipun, adalah bahwa Anda merasa sulit untuk memutuskan apakah hasilnya adalah benar, maka itu adalah masalah yang sama sekali berbeda. Misalnya, Anda memiliki metode yang mendeteksi apakah angka besar yang sewenang-wenang adalah prima. Anda tidak dapat melemparkan angka acak ke sana dan kemudian hanya "melihat" jika hasilnya benar (dengan asumsi Anda tidak dapat menentukan keunggulan di kepala Anda atau di selembar kertas). Dalam hal ini, memang ada sedikit yang bisa Anda lakukan - Anda harus mendapatkan hasil yang diketahui (yaitu, beberapa bilangan prima besar), atau mengimplementasikan fungsionalitas dengan algoritma yang berbeda (mungkin bahkan tim yang berbeda - NASA tampaknya menyukai itu) dan berharap bahwa jika salah satu implementasi bermasalah, setidaknya bug tidak mengarah ke hasil yang salah sama.

Jika ini adalah kasus biasa bagi Anda, maka Anda harus bicara keras dengan insinyur kebutuhan Anda. Jika mereka tidak dapat merumuskan persyaratan Anda dengan cara yang mudah (atau mungkin) untuk memeriksa Anda, lalu kapan Anda tahu apakah Anda sudah selesai?

AnoE
sumber
2

Jawaban lain baik, jadi saya akan mencoba untuk mencapai beberapa poin yang secara kolektif mereka lewatkan sejauh ini.

Saya telah menulis (dan benar-benar diuji) perangkat lunak untuk melakukan pemrosesan gambar menggunakan Radar Aperture Sintetis (SAR). Ini ilmiah / numerik di alam (ada banyak geometri, fisika, dan matematika yang terlibat).

Beberapa tips (untuk pengujian ilmiah / numerik umum):

1) Gunakan invers. Apa fftof [1,2,3,4,5]? Tidak ada ide. Apa ifft(fft([1,2,3,4,5]))? Seharusnya [1,2,3,4,5](atau dekat dengan itu, kesalahan floating point mungkin muncul). Hal yang sama berlaku untuk case 2D.

2) Gunakan pernyataan yang dikenal. Jika Anda menulis fungsi determinan, mungkin sulit untuk mengatakan apa determinannya dari matriks 100x100 acak. Tapi Anda tahu bahwa penentu matriks identitas adalah 1, bahkan jika itu 100x100. Anda juga tahu bahwa fungsi tersebut harus mengembalikan 0 pada matriks yang tidak dapat dibalik (seperti 100x100 penuh dari semua 0s).

3) Gunakan konfirmasi kasar, bukan konfirmasi yang tepat . Saya menulis beberapa kode untuk pemrosesan SAR yang akan mendaftarkan dua gambar dengan menghasilkan titik pengikat yang membuat pemetaan antara gambar dan kemudian melakukan lungsin di antara mereka untuk membuatnya cocok. Itu bisa mendaftar pada tingkat sub-pixel. A priori, sulit untuk mengatakan apa -apa tentang bagaimana pendaftaran dua gambar akan terlihat. Bagaimana Anda bisa mengujinya? Hal-hal seperti:

EXPECT_TRUE(register(img1, img2).size() < min(img1.size(), img2.size()))

karena Anda hanya dapat mendaftar pada bagian yang tumpang tindih, gambar yang terdaftar harus lebih kecil atau sama dengan gambar terkecil Anda, dan juga:

scale = 255
EXPECT_PIXEL_EQ_WITH_TOLERANCE(reg(img, img), img, .05*scale)

karena gambar yang didaftarkan ke dirinya sendiri harus TUTUP untuk dirinya sendiri, tetapi Anda mungkin mengalami sedikit lebih banyak dari kesalahan floating point karena algoritma yang ada, jadi cukup periksa setiap piksel dalam +/- 5% dari rentang piksel yang dapat diambil (0-255 berwarna abu-abu, umum dalam pemrosesan gambar). Hasil setidaknya harus berukuran sama dengan input.

Anda bahkan dapat hanya merokok tes (mis. Panggilan itu dan pastikan itu tidak crash). Secara umum, teknik ini lebih baik untuk tes yang lebih besar di mana hasil akhirnya tidak dapat (dengan mudah) dihitung secara apriori untuk menjalankan tes.

4) Gunakan ATAU MENYIMPAN seed number acak untuk RNG Anda.

Berjalan memang harus direproduksi. Adalah salah, bagaimanapun, bahwa satu-satunya cara untuk mendapatkan proses yang dapat direproduksi adalah dengan menyediakan benih khusus untuk generator angka acak. Terkadang pengujian keacakan bernilai. Saya telah melihat / mendengar tentang bug dalam kode ilmiah yang muncul dalam kasus degenerasi yang dihasilkan secara acak (dalam algoritma yang rumit mungkin sulit untuk melihat apa kasus degenerasi itu)). Alih-alih selalu memanggil fungsi Anda dengan seed yang sama, hasilkan seed acak, lalu gunakan seed itu, dan catat nilai seed tersebut. Dengan begitu setiap proses memiliki seed acak berbeda, tetapi jika Anda mendapatkan crash, Anda dapat menjalankan kembali hasilnya dengan menggunakan seed yang telah Anda login ke debug. Saya sebenarnya telah menggunakan ini dalam praktek dan itu menghancurkan bug, jadi saya pikir saya akan menyebutkannya. Memang ini hanya terjadi sekali, dan saya yakin itu tidak selalu layak dilakukan, jadi gunakan teknik ini dengan bijaksana. Acak dengan benih yang sama selalu aman. Kekurangan (bukan hanya menggunakan seed yang sama sepanjang waktu): Anda harus mencatat uji coba Anda. Terbalik: Koreksi dan bug nuking.

Kasus khusus Anda

1) Uji bahwa kosong taskArray mengembalikan 0 (dikenal menegaskan).

2) Menghasilkan input acak sehingga task.time > 0 , task.due > 0, dan task.importance > 0 untuk semua task s, dan menegaskan hasilnya lebih besar dari 0 (menegaskan kasar, masukan acak) . Anda tidak perlu menjadi gila dan menghasilkan benih acak, algoritma Anda tidak cukup kompleks untuk menjaminnya. Ada sekitar 0 kesempatan itu akan terbayar: tetap uji sederhana.

3) Uji jika task.importance == 0 untuk semua task s, maka hasilnya adalah 0 (dikenal dengan tegas)

4) Jawaban lain menyentuh ini, tetapi mungkin penting untuk kasus khusus Anda : Jika Anda membuat API untuk dikonsumsi oleh pengguna di luar tim Anda, Anda perlu menguji kasus yang merosot. Misalnya, jika workPerDay == 0, pastikan Anda melempar kesalahan indah yang memberi tahu pengguna bahwa input tidak valid. Jika Anda tidak membuat API, dan itu hanya untuk Anda dan tim Anda, Anda mungkin dapat melewati langkah ini, dan hanya menolak untuk memanggilnya dengan case degenerate.

HTH.

Matt Messersmith
sumber
1

Menggabungkan pengujian asersi ke dalam unit test unit Anda untuk pengujian berbasis properti dari algoritma Anda. Selain menulis unit test yang memeriksa output spesifik, tes menulis dirancang untuk gagal dengan memicu kegagalan pernyataan dalam kode utama.

Banyak algoritma mengandalkan bukti kebenarannya pada mempertahankan properti tertentu di seluruh tahapan algoritma. Jika Anda dapat memeriksa properti ini secara masuk akal dengan melihat output dari suatu fungsi, pengujian unit saja sudah cukup untuk menguji properti Anda. Jika tidak, pengujian berbasis pernyataan memungkinkan Anda menguji bahwa suatu implementasi mempertahankan properti setiap kali algoritma mengasumsikannya.

Pengujian berbasis pernyataan akan mengekspos kekurangan algoritma, bug pengkodean, dan kegagalan implementasi karena masalah seperti ketidakstabilan numerik. Banyak bahasa memiliki mekanisme menghapus pernyataan pada waktu kompilasi atau sebelum kode ditafsirkan sehingga ketika dijalankan dalam mode produksi pernyataan tersebut tidak dikenakan penalti kinerja. Jika kode Anda lulus pengujian unit tetapi gagal pada kasus nyata, Anda dapat mengaktifkan kembali pernyataan sebagai alat debugging.

Tobias Hagge
sumber
1

Beberapa jawaban lain di sini sangat bagus:

  • Tempat uji, tepi, dan sudut kasus
  • Lakukan pemeriksaan kewarasan
  • Lakukan tes perbandingan

... Saya akan menambahkan beberapa taktik lain:

  • Uraikan masalahnya.
  • Buktikan algoritma di luar kode.
  • Uji bahwa algoritma [yang terbukti secara eksternal] diimplementasikan sebagai yang dirancang.

Dekomposisi memungkinkan Anda memastikan komponen algoritme Anda melakukan apa yang Anda harapkan. Dan dekomposisi yang "baik" memungkinkan Anda juga memastikan bahwa keduanya direkatkan dengan benar. Sebuah besar dekomposisi generalisasi dan menyederhanakan algoritma untuk sejauh yang Anda bisa memprediksi hasil (dari disederhanakan, algoritma generik (s)) dengan tangan cukup baik untuk menulis tes menyeluruh.

Jika Anda tidak dapat menguraikan sejauh itu, buktikan algoritma di luar kode dengan cara apa pun yang cukup untuk memuaskan Anda dan rekan kerja, pemangku kepentingan, dan pelanggan Anda. Dan kemudian, cukup dekomposisi untuk membuktikan implementasi Anda cocok dengan desain.

svidgen
sumber
0

Ini mungkin tampak seperti jawaban idealis tetapi membantu mengidentifikasi berbagai jenis pengujian.

Jika jawaban ketat penting untuk implementasi maka contoh dan jawaban yang diharapkan benar-benar harus disediakan dalam persyaratan yang menggambarkan algoritma. Persyaratan ini harus ditinjau kelompok dan jika Anda tidak mendapatkan hasil yang sama, alasannya perlu diidentifikasi.

Sekalipun Anda berperan sebagai analis dan juga pelaksana, Anda harus benar-benar membuat persyaratan dan memeriksanya jauh sebelum Anda menulis tes unit, jadi dalam hal ini Anda akan mengetahui hasil yang diharapkan dan dapat menulis tes sesuai dengan itu.

Di sisi lain, jika ini adalah bagian yang Anda laksanakan yang bukan bagian dari logika bisnis atau mendukung jawaban logika bisnis, maka tidak masalah untuk menjalankan tes untuk melihat apa hasilnya dan kemudian memodifikasi tes yang diharapkan hasil itu. Hasil akhir sudah diperiksa terhadap persyaratan Anda sehingga jika mereka benar maka semua kode yang memberi makan hasil akhir harus benar secara numerik dan pada saat itu tes unit Anda lebih untuk mendeteksi kasus kegagalan tepi dan perubahan refactoring di masa depan daripada untuk membuktikan bahwa diberikan algoritma menghasilkan hasil yang benar.

Bill K
sumber
0

Saya pikir itu dapat diterima pada kesempatan untuk mengikuti proses:

  • rancang kasus uji
  • gunakan perangkat lunak Anda untuk mendapatkan jawabannya
  • periksa jawabannya dengan tangan
  • tulis uji regresi sehingga versi perangkat lunak yang akan datang akan terus memberikan jawaban ini.

Ini adalah pendekatan yang masuk akal dalam situasi apa pun di mana memeriksa kebenaran jawaban dengan tangan lebih mudah daripada menghitung jawaban dengan tangan dari prinsip pertama.

Saya tahu orang-orang yang menulis perangkat lunak untuk merender halaman yang dicetak, dan memiliki tes yang memeriksa apakah piksel yang tepat sudah diatur pada halaman yang dicetak. Satu-satunya cara yang waras untuk melakukannya adalah dengan menulis kode untuk membuat halaman, memeriksa dengan mata bahwa itu terlihat bagus, dan kemudian menangkap hasilnya sebagai tes regresi untuk rilis mendatang.

Hanya karena Anda membaca di sebuah buku yang mendorong metodologi tertentu untuk menulis kasus ujian terlebih dahulu, tidak berarti Anda harus selalu melakukannya dengan cara itu. Aturan ada untuk dilanggar.

Michael Kay
sumber
0

Jawaban lain jawaban sudah memiliki teknik untuk apa tes tampak ketika hasil spesifik tidak dapat ditentukan di luar fungsi yang diuji.

Apa yang saya lakukan sebagai tambahan yang belum saya temukan dalam jawaban lain adalah untuk membuat tes secara otomatis dengan beberapa cara:

  1. Input 'acak'
  2. Iterasi lintas rentang data
  3. Konstruksi kasus uji dari set batas
  4. Semua yang di atas.

Misalnya, jika fungsi tersebut masing-masing mengambil tiga parameter dengan rentang input yang diizinkan [-1,1], uji semua kombinasi dari setiap parameter, {-2, -1,01, -1, -0,99, -0,5, -0,01, -0,01, 0,0,01 , 0,5,0,99,1,1,01,2, beberapa lebih acak dalam (-1,1)}

Singkatnya: Terkadang kualitas yang buruk dapat disubsidi oleh kuantitas.

Keith
sumber