Apakah cakupan jalur menjamin menemukan semua bug?

64

Jika setiap jalur melalui program diuji, apakah itu menjamin menemukan semua bug?

Jika tidak, mengapa tidak? Bagaimana Anda bisa melalui setiap kombinasi yang mungkin dari aliran program dan tidak menemukan masalah jika ada?

Saya ragu untuk menyarankan bahwa "semua bug" dapat ditemukan, tetapi mungkin itu karena cakupan path tidak praktis (seperti kombinatorial) sehingga tidak pernah dialami?

Catatan: artikel ini memberikan ringkasan singkat tentang jenis cakupan seperti yang saya pikirkan.


sumber
33
Ini sama dengan masalah penghentian .
31
Bagaimana jika kode yang seharusnya ada di sana, bukan?
RemcoGerlich
6
@Snowman: Tidak, tidak. Adalah tidak mungkin untuk memecahkan masalah penghentian untuk semua program tetapi untuk banyak program spesifik dapat dipecahkan. Untuk program-program ini, semua jalur kode dapat disebutkan dalam jumlah waktu yang terbatas (meskipun mungkin lama).
Jørgen Fogh
3
@ JørgenFogh Tetapi ketika mencoba menemukan bug di program apa pun , bukankah apriori tidak diketahui apakah programnya terhenti atau tidak? Bukankah pertanyaan ini tentang metode umum "menemukan semua bug dalam program apa pun melalui cakupan jalur"? Dalam hal ini, bukankah itu mirip dengan "menemukan apakah ada program yang berhenti"?
Andres F.
1
@AndresF. hanya tidak diketahui apakah program berhenti jika bagian dari bahasa yang ditulisnya mampu mengekspresikan program yang tidak berhenti. Jika program Anda ditulis dalam C tanpa menggunakan loop tak terbatas / rekursi / setjmp dll, atau dalam Coq, atau dalam ESSL, maka ia harus berhenti dan semua jalur dapat dilacak. (Kelengkapan-Turing benar-benar berlebihan)
Leushenko

Jawaban:

128

Jika setiap jalur melalui program diuji, apakah itu menjamin menemukan semua bug?

Tidak

Jika tidak, mengapa tidak? Bagaimana Anda bisa melalui setiap kombinasi yang mungkin dari aliran program dan tidak menemukan masalah jika ada?

Karena bahkan jika Anda menguji semua jalur yang mungkin , Anda masih belum mengujinya dengan semua nilai yang mungkin atau semua kemungkinan kombinasi nilai . Misalnya (kodesemu):

def Add(x as Int32, y as Int32) as Int32:
   return x + y

Test.Assert(Add(2, 2) == 4) //100% test coverage
Add(MAXINT, 5) //Throws an exception, despite 100% test coverage

Sekarang sudah dua dekade sejak ditunjukkan bahwa pengujian program dapat secara meyakinkan menunjukkan keberadaan bug, tetapi tidak pernah dapat menunjukkan ketidakhadiran mereka. Setelah mengutip pernyataan yang dipublikasikan dengan baik ini, insinyur perangkat lunak kembali ke urutan hari dan terus memperbaiki strategi pengujiannya, seperti alkemis dahulu kala, yang terus menyempurnakan pemurnian chrysocosmic-nya.

- EW Dijkstra (Penekanan ditambahkan. Ditulis pada tahun 1988. Sudah lebih dari 2 dekade sekarang.)

Mason Wheeler
sumber
7
@digitgopher: Saya kira, tetapi jika suatu program tidak memiliki input, apa manfaatnya?
Mason Wheeler
34
Ada juga kemungkinan tidak adanya tes integrasi, bug dalam pengujian, bug dalam dependensi, bug dalam sistem build / deployment, atau bug dalam spesifikasi / persyaratan asli. Anda tidak pernah bisa menjamin menemukan semua bug.
Ixrec
11
@Irecrec: SQLite membuat upaya yang cukup berani! Tetapi lihatlah betapa luar biasanya upaya ini! Itu tidak akan skala baik untuk basis kode besar.
Mason Wheeler
13
Anda tidak hanya tidak menguji semua nilai yang mungkin atau kombinasi daripadanya, Anda belum menguji semua waktu relatif, beberapa di antaranya dapat mengekspos kondisi balapan atau memang membuat tes Anda memasuki jalan buntu, yang akan membuatnya gagal melaporkan apa pun . Itu bahkan tidak akan gagal!
Iwillnotexist Idonotexist
14
Ingatan saya (didukung oleh tulisan-tulisan seperti ini ) adalah bahwa Dijkstra percaya bahwa dalam praktik pemrograman yang baik, bukti bahwa suatu program benar (dalam semua kondisi) harus menjadi bagian integral dari pengembangan program di tempat pertama. Dilihat dari sudut pandang itu, pengujian adalah seperti alkimia. Daripada hiperbola, saya pikir ini adalah pendapat yang sangat kuat yang diungkapkan dalam bahasa yang sangat kuat.
David K
71

Selain jawaban Mason , ada juga masalah lain: cakupan tidak memberi tahu Anda kode apa yang diuji, ia memberi tahu Anda kode apa yang dieksekusi .

Bayangkan Anda memiliki testuite dengan cakupan jalur 100%. Sekarang hapus semua asersi dan jalankan testuite lagi. Voa, testuite masih memiliki cakupan jalur 100%, tetapi tidak ada tes sama sekali.

Jörg W Mittag
sumber
2
Itu bisa memastikan bahwa tidak ada pengecualian saat memanggil kode yang diuji (dengan parameter dalam tes). Ini sedikit lebih dari tidak sama sekali.
Paŭlo Ebermann
7
@ PaŭloEbermann Setuju, sedikit lebih dari tidak sama sekali. Namun, ini sangat kurang dari "menemukan semua bug";)
Andres F.
1
@ PaŭloEbermann: Pengecualian adalah jalur kode. Jika kode bisa melempar tetapi dengan data uji tertentu tidak melempar, tes tidak mencapai cakupan jalur 100%. Ini tidak spesifik untuk pengecualian sebagai mekanisme penanganan kesalahan. Visual Basic ON ERROR GOTOjuga jalan, seperti C if(errno).
MSalters
1
@ MSalters Saya sedang berbicara tentang kode yang (menurut spesifikasi) tidak boleh membuang pengecualian, terlepas dari input. Jika ada yang melempar, itu akan menjadi bug. Tentu saja, jika Anda memiliki kode yang ditentukan untuk melempar pengecualian, itu harus diuji. (Dan tentu saja, seperti kata Jorg, hanya memeriksa bahwa kode tidak membuang pengecualian biasanya tidak cukup untuk memastikan itu melakukan hal yang benar, bahkan untuk kode non-lempar.) Dan beberapa pengecualian dapat dilemparkan oleh non -lihat kode jalur, seperti untuk null pointer dereference atau pembagian dengan nol. Apakah alat cakupan jalur Anda menangkapnya?
Paŭlo Ebermann
2
Jawaban ini benar. Saya akan mengambil klaim lebih jauh dan mengatakan bahwa karena ini, cakupan jalur tidak pernah menjamin untuk menemukan bahkan satu bug pun. Ada metrik yang dapat menjamin setidaknya bahwa perubahan akan terdeteksi, namun - pengujian mutasi sebenarnya dapat menjamin bahwa (beberapa) modifikasi kode akan terdeteksi.
eis
34

Berikut adalah contoh sederhana untuk menyelesaikan masalah. Pertimbangkan algoritma pengurutan berikut (di Jawa):

int[] sort(int[] x) { return new int[] { x[0] }; }

Sekarang, mari kita coba:

sort(new int[] { 0xCAFEBABE });

Sekarang, pertimbangkan bahwa (A) panggilan khusus ini untuk sortmengembalikan hasil yang benar, (B) semua jalur kode telah dicakup oleh tes ini.

Tapi, jelas, program itu sebenarnya tidak mengurutkan.

Oleh karena itu cakupan semua jalur kode tidak cukup untuk menjamin bahwa program tidak memiliki bug.

Atsby
sumber
12

Pertimbangkan absfungsi, yang mengembalikan nilai absolut suatu angka. Berikut ini adalah tes (Python, bayangkan beberapa kerangka uji):

def test_abs_of_neg_number_returns_positive():
    assert abs(-3) == 3

Implementasi ini benar, tetapi hanya mendapat cakupan kode 60%:

def abs(x):
    if x < 0:
        return -x
    else:
        return x

Implementasi ini salah, tetapi mendapat 100% cakupan kode:

def abs(x):
    return -x
RemcoGerlich
sumber
2
Berikut ini adalah implementasi lain yang lulus tes (tolong maafkan Python non-linebroken): def abs(x): if x == -3: return 3 else: return 0Anda mungkin bisa menghilangkan else: return 0bagian dan mendapatkan cakupan 100%, tetapi fungsi ini pada dasarnya tidak akan berguna meskipun itu melewati unit test.
CVn
7

Namun tambahan lain untuk jawaban Mason , perilaku program mungkin tergantung pada lingkungan runtime.

Kode berikut berisi Use-After-Free:

int main(void)
{
    int* a = malloc(sizeof(a));
    int* b = a;
    *a = 0;
    free(a);
    *b = 12; /* UAF */
    return 0;
}

Kode ini adalah Perilaku Tidak Terdefinisi, tergantung pada konfigurasi (rilis | debug), OS dan kompiler akan menghasilkan perilaku yang berbeda. Tidak hanya cakupan jalur tidak akan menjamin bahwa Anda akan menemukan UAF, tetapi paket pengujian Anda biasanya tidak akan mencakup berbagai perilaku yang mungkin dari UAF yang bergantung pada konfigurasi.

Pada catatan lain, bahkan jika cakupan jalur adalah untuk menjamin menemukan semua bug, tidak mungkin hal itu dapat dicapai dalam praktiknya pada program apa pun. Pertimbangkan yang berikut ini:

int main(int a, int b)
{
    if (a != b) {
        if (cryptohash(a) == cryptohash(b)) {
            return ERROR;
        }
    }
    return 0;
} 

Jika test-suite Anda dapat menghasilkan semua jalur untuk ini, maka selamat Anda adalah seorang cryptographer.

dureuill
sumber
Mudah untuk bilangan bulat yang cukup kecil :)
CodesInChaos
Tanpa mengetahui apa-apa cryptohash, agak sulit untuk mengatakan apa itu "cukup kecil". Mungkin butuh dua hari untuk menyelesaikan supercalculator. Tapi ya, intmungkin sedikit berubah short.
dureuill
Dengan bilangan bulat 32 bit dan hash kriptografi tipikal (SHA2, SHA3, dll.) Ini seharusnya cukup murah. Beberapa detik atau lebih.
CodesInChaos
7

Jelas dari jawaban lain bahwa cakupan kode 100% dalam pengujian tidak berarti kebenaran kode 100%, atau bahkan semua bug yang dapat ditemukan dengan pengujian, akan ditemukan (apalagi bug yang tidak dapat dites oleh tes apa pun).

Cara lain untuk menjawab pertanyaan ini adalah dari praktik:

Ada, di dunia nyata, dan memang di komputer Anda sendiri, banyak perangkat lunak yang dikembangkan menggunakan serangkaian tes yang memberikan cakupan 100% dan yang masih memiliki bug, termasuk bug yang sebaiknya diidentifikasi oleh pengujian lebih baik.

Oleh karena itu pertanyaan yang disyaratkan, adalah:

Apa gunanya alat cakupan kode?

Alat-alat cakupan kode membantu mengidentifikasi bidang-bidang yang telah diabaikan untuk diuji. Itu bisa baik-baik saja (kode terbukti benar bahkan tanpa pengujian) mungkin tidak dapat diselesaikan (untuk beberapa alasan jalur tidak dapat dipukul), atau dapat menjadi lokasi bug bau besar baik sekarang atau mengikuti modifikasi di masa depan.

Dalam beberapa hal, pemeriksa ejaan dapat dibandingkan: Sesuatu dapat "lulus" periksa ejaan dan salah eja sedemikian rupa sehingga cocok dengan kata dalam kamus. Atau bisa "gagal" karena kata-kata yang benar tidak ada dalam kamus. Atau bisa lewat dan menjadi omong kosong. Pemeriksaan ejaan adalah alat yang membantu Anda mengidentifikasi tempat-tempat yang mungkin Anda lewatkan dalam pembacaan bukti Anda, tetapi sama seperti itu tidak dapat menjamin pembacaan bukti yang lengkap dan benar, sehingga cakupan kode tidak dapat menjamin pengujian yang lengkap dan benar.

Dan tentu saja cara yang salah untuk menggunakan pemeriksaan ejaan adalah terkenal dengan setiap saran di laut yang disarankan sehingga hal yang merunduk menjadi lebih buruk maka jika kita meninggalkannya pinjaman.

Dengan cakupan kode bisa menggoda, terutama jika Anda memiliki 98% hampir sempurna, untuk mengisi kasus sehingga jalur yang tersisa terpukul.

Itu sama dengan meluruskan dengan pemeriksaan ejaan bahwa itu semua kata cuaca atau simpul itu semua kata yang tepat. Hasilnya berantakan.

Namun, jika Anda mempertimbangkan tes apa yang dibutuhkan jalur yang tidak tercakup, alat cakupan kode akan melakukan tugasnya; tidak dalam menjanjikan Anda kebenaran, tetapi itu menunjukkan beberapa pekerjaan yang perlu dilakukan.

Jon Hanna
sumber
+1 Saya suka jawaban ini karena konstruktif dan menyebutkan beberapa manfaat pertanggungan.
Andres F.
4

Cakupan jalur tidak dapat memberi tahu Anda apakah semua fitur yang diperlukan telah diterapkan. Meninggalkan fitur adalah bug, tetapi cakupan jalur tidak akan mendeteksinya.

Pete Becker
sumber
1
Saya pikir itu tergantung pada definisi bug. Saya tidak berpikir fitur atau fungsionalitas yang hilang harus dianggap sebagai bug.
eis
@ eis - Anda tidak melihat masalah dengan produk yang dokumentasinya mengatakan bahwa ia melakukan X padahal sebenarnya tidak? Itu definisi yang agak sempit tentang "bug". Ketika saya mengelola QA untuk lini produk C ++ Borland, kami tidak semurah itu.
Pete Becker
Saya tidak melihat mengapa dokumentasi mengatakan itu X jika itu tidak pernah dilaksanakan
eis
@is - jika desain asli meminta fitur X, dokumentasi dapat berakhir dengan menggambarkan fitur X. Jika tidak ada yang mengimplementasikannya, itu bug, dan cakupan jalur (atau jenis pengujian kotak hitam lainnya) tidak akan menemukannya.
Pete Becker
Ups, cakupan jalur adalah pengujian kotak putih , bukan kotak hitam . Pengujian kotak putih tidak dapat menangkap fitur yang hilang.
Pete Becker
4

Bagian dari masalah adalah bahwa cakupan 100% hanya menjamin bahwa kode akan berfungsi dengan benar setelah satu eksekusi . Beberapa bug seperti kebocoran memori mungkin tidak terlihat atau menyebabkan masalah setelah satu eksekusi, tetapi seiring waktu akan menyebabkan masalah untuk aplikasi.

Misalnya, Anda memiliki aplikasi yang terhubung ke database. Mungkin dalam satu metode programmer lupa untuk menutup koneksi ke database ketika mereka selesai dengan permintaan mereka. Anda bisa menjalankan beberapa tes pada metode ini dan tidak menemukan kesalahan dengan fungsionalitasnya, tetapi server database Anda mungkin mengalami skenario di mana tidak ada koneksi yang tersedia karena metode khusus ini tidak menutup koneksi ketika itu dilakukan dan koneksi terbuka harus sekarang batas waktu.

Derek W
sumber
Setuju bahwa itu adalah bagian dari masalah, tetapi masalah sebenarnya lebih mendasar dari itu. Bahkan dengan komputer teoretis dengan memori tak terbatas dan tanpa konkurensi, cakupan uji 100% tidak menyiratkan tidak adanya bug. Contoh sepele dari ini berlimpah dalam jawaban di sini, tapi di sini ada yang lain: jika program saya times_two(x) = x + 2, ini akan sepenuhnya ditanggung oleh test suite assert(times_two(2) == 4), tetapi ini masih jelas kode kereta! Tidak perlu kebocoran memori :)
Andres F.
2
Ini adalah poin yang bagus dan saya menyadari bahwa ini adalah paku yang lebih besar / lebih mendasar dalam peti mati kemungkinan aplikasi bebas bug, tetapi seperti yang Anda katakan itu sudah ditambahkan di sini dan saya ingin menambahkan sesuatu yang tidak cukup dibahas. jawaban yang ada. Saya telah mendengar tentang aplikasi yang macet karena koneksi basis data tidak dilepaskan kembali ke kolam koneksi ketika mereka tidak lagi diperlukan - Kebocoran memori hanyalah contoh kan saja dari kesalahan pengelolaan sumber daya. Maksud saya adalah menambahkan bahwa manajemen sumber daya yang tepat secara umum tidak dapat sepenuhnya diuji.
Derek W
Poin bagus. Sepakat.
Andres F.
3

Jika setiap jalur melalui program diuji, apakah itu menjamin menemukan semua bug?

Seperti yang sudah dikatakan, jawabannya adalah TIDAK.

Jika tidak, mengapa tidak?

Selain apa yang dikatakan, ada bug yang muncul di tingkat yang berbeda, yang tidak dapat diuji dengan tes unit. Untuk menyebutkan beberapa saja:

  • bug tertangkap dengan tes integrasi (tes unit seharusnya tidak menggunakan sumber daya nyata setelah semua)
  • bug dalam persyaratan
  • bug dalam desain dan arsitektur
BЈовић
sumber
2

Apa artinya bagi setiap jalur untuk diuji?

Jawaban lainnya bagus, tetapi saya hanya ingin menambahkan bahwa kondisi "setiap jalur melalui program diuji" itu sendiri tidak jelas.

Pertimbangkan metode ini:

def add(num1, num2)
  foo = "bar"  # useless statement
  $global += 1 # side effect
  num1 + num2  # actual work
end

Jika Anda menulis tes yang menegaskan add(1, 2) == 3, alat cakupan kode akan memberi tahu Anda bahwa setiap baris dilakukan. Tetapi Anda belum benar-benar menyatakan apa pun tentang efek samping global atau tugas yang tidak berguna. Garis-garis itu dieksekusi, tetapi belum benar-benar diuji.

Pengujian mutasi akan membantu menemukan masalah seperti ini. Alat pengujian mutasi akan memiliki daftar cara yang telah ditentukan sebelumnya untuk "mengubah" kode dan melihat apakah tes masih lulus. Sebagai contoh:

  • Satu mutasi dapat mengubah +=ke -=. Mutasi itu tidak akan menyebabkan kegagalan tes, jadi itu akan membuktikan bahwa tes Anda tidak menyatakan sesuatu yang berarti tentang efek samping global.
  • Mutasi lain mungkin menghapus baris pertama. Mutasi itu tidak akan menyebabkan kegagalan tes, jadi itu akan membuktikan bahwa tes Anda tidak menyatakan sesuatu yang bermakna tentang tugas tersebut.
  • Mutasi lain mungkin menghapus baris ketiga. Itu akan menyebabkan kegagalan tes, yang dalam kasus ini, menunjukkan bahwa tes Anda memang menegaskan sesuatu tentang garis itu.

Pada dasarnya, tes mutasi adalah cara untuk menguji tes Anda . Tapi sama seperti Anda tidak akan pernah menguji fungsi aktual dengan setiap set input yang mungkin, Anda tidak akan pernah menjalankan setiap mutasi yang mungkin, jadi sekali lagi, ini terbatas.

Setiap tes yang dapat kita lakukan adalah heuristik untuk bergerak menuju program bebas bug. Tidak ada yang sempurna.

Nathan Long
sumber
0

Yah ... ya sebenarnya, jika setiap jalur "melalui" program ini diuji. Tetapi itu berarti, setiap jalur yang mungkin melalui seluruh ruang dari semua status yang mungkin dimiliki program, termasuk semua variabel. Bahkan untuk program yang dikompilasi secara statis sangat sederhana - katakanlah, pembuat angka Fortran lama - itu tidak layak, meskipun setidaknya dapat dibayangkan: jika Anda hanya memiliki dua variabel integer, Anda pada dasarnya berurusan dengan semua cara yang mungkin untuk menghubungkan titik-titik pada kisi dua dimensi; sebenarnya sangat mirip dengan Travelling Salesman. Untuk n variabel seperti itu, Anda berhadapan dengan ruang n- dimensi, jadi untuk setiap program nyata, tugas tersebut sama sekali tidak dapat dilakukan.

Lebih buruk: untuk hal-hal serius, Anda tidak hanya memiliki sejumlah variabel primitif, tetapi membuat variabel dengan cepat dalam panggilan fungsi, atau memiliki variabel ukuran variabel ... atau hal-hal seperti itu, mungkin dalam bahasa lengkap Turing. Itu membuat ruang ruang dimensi tak terbatas, menghancurkan semua harapan cakupan penuh, bahkan diberikan peralatan pengujian yang sangat kuat.


Yang mengatakan ... sebenarnya hal-hal tidak begitu suram. Hal ini dimungkinkan untuk proove seluruh program untuk menjadi benar, tetapi Anda harus menyerah beberapa ide.

Pertama: sangat disarankan untuk beralih ke bahasa deklaratif. Bahasa imperatif, untuk beberapa alasan, selalu menjadi yang paling populer, tetapi cara mereka menggabungkan algoritma dengan interaksi dunia nyata membuatnya sangat sulit untuk mengatakan apa yang Anda maksud dengan "benar".

Jauh lebih mudah dalam bahasa pemrograman yang benar-benar fungsional : ini memiliki perbedaan yang jelas antara sifat-sifat fungsi matematika yang sangat menarik , dan interaksi dunia nyata yang tidak dapat Anda katakan. Untuk fungsi, sangat mudah untuk menentukan "perilaku yang benar": jika untuk semua input yang mungkin (dari tipe argumen) hasil yang diinginkan keluar, maka fungsi tersebut berperilaku dengan benar.

Sekarang, Anda mengatakan itu masih sulit dilakukan ... setelah semua, ruang dari semua argumen yang mungkin pada umumnya juga tak terbatas-dimensi. Benar - meskipun untuk satu fungsi, bahkan pengujian cakupan naif membawa Anda lebih jauh dari yang Anda bisa harapkan dalam program penting! Namun, ada alat yang luar biasa kuat yang mengubah permainan: kuantifikasi universal / polimorfisme parametrik . Pada dasarnya, ini memungkinkan Anda untuk menulis fungsi pada jenis data yang sangat umum, dengan jaminan bahwa jika itu berfungsi untuk contoh data yang sederhana, ia akan bekerja untuk setiap input yang mungkin sama sekali.

Setidaknya secara teoritis. Tidak mudah menemukan jenis yang tepat yang sangat umum sehingga Anda dapat sepenuhnya membuktikan hal ini - biasanya, Anda memerlukan bahasa yang diketik secara dependen , dan ini cenderung agak sulit untuk digunakan. Tetapi menulis dengan gaya fungsional dengan polimorfisme parametrik saja sudah meningkatkan "tingkat keamanan" Anda dengan sangat baik - Anda tidak perlu menemukan semua bug, tetapi Anda harus menyembunyikannya dengan baik sehingga kompiler tidak melihatnya!

leftaroundabout
sumber
Saya tidak setuju dengan kalimat pertama Anda. Melewati setiap keadaan program tidak dengan sendirinya mendeteksi adanya bug. Sekalipun Anda memeriksa kerusakan dan kesalahan eksplisit, Anda masih belum memeriksa fungsi sebenarnya dengan cara apa pun, jadi Anda hanya membahas sebagian kecil ruang kesalahan.
Matius Baca
@ MatthewRead: jika Anda menerapkan ini secara konsekuen, maka "ruang kesalahan" adalah ruang bagian yang tepat dari ruang semua negara. Tentu saja ini hipotetis karena bahkan "benar" negara membuat ruang yang terlalu besar untuk memungkinkan tes lengkap.
leftaroundabout