Saya tidak dapat menunjuk ke sumber daring yang bagus (artikel Wikipedia bahasa Inggris tentang topik ini cenderung tidak dapat diperbaiki), tetapi saya dapat merangkum ceramah yang saya dengar yang juga membahas teori pengujian dasar.
Mode pengujian
Ada beberapa kelas tes, seperti tes unit atau tes integrasi . Tes unit menyatakan bahwa sepotong kode yang koheren (fungsi, kelas, modul) diambil sesuai dengan fungsinya sendiri, sedangkan tes integrasi menyatakan bahwa beberapa bagian tersebut bekerja dengan benar bersama-sama.
Sebuah test case adalah lingkungan yang dikenal di mana sepotong kode dieksekusi, misalnya dengan menggunakan input tes khusus, atau dengan mengejek kelas lain. Perilaku kode kemudian dibandingkan dengan perilaku yang diharapkan, misalnya nilai pengembalian spesifik.
Sebuah tes hanya dapat membuktikan adanya bug, tidak pernah tidak adanya semua bug. Tes menempatkan batas atas kebenaran program.
Cakupan Kode
Untuk menentukan metrik cakupan kode, kode sumber dapat diterjemahkan ke grafik aliran kontrol di mana setiap node berisi segmen linier dari kode. Kontrol mengalir antara node-node ini hanya di akhir setiap blok, dan selalu bersyarat (jika kondisinya, maka goto node A, kalau tidak goto node B). Grafik memiliki satu simpul mulai dan satu simpul ujung.
- Dengan grafik ini, cakupan pernyataan adalah rasio dari semua node yang dikunjungi ke semua node. Cakupan pernyataan lengkap tidak cukup untuk pengujian menyeluruh.
- Cakupan cabang adalah rasio dari semua tepi yang dikunjungi antara node dalam CFG dengan semua tepi. Ini tidak cukup menguji loop.
- Cakupan jalur adalah rasio dari semua jalur yang dikunjungi ke semua jalur, di mana jalur adalah urutan tepi mana pun dari awal hingga akhir. Masalahnya adalah bahwa dengan loop, mungkin ada jumlah lintasan yang tidak terbatas, sehingga cakupan lintasan penuh tidak dapat diuji secara praktis.
Oleh karena itu seringkali berguna untuk memeriksa cakupan kondisi .
- Dalam cakupan kondisi sederhana , setiap kondisi atom pernah benar dan salah sekali - tetapi ini tidak menjamin cakupan pernyataan penuh.
- Dalam cakupan beberapa kondisi , kondisi atom telah mengambil semua kombinasi
true
dan false
. Ini menyiratkan cakupan cabang penuh, tetapi agak mahal. Program mungkin memiliki kendala tambahan yang mengecualikan kombinasi tertentu. Teknik ini baik untuk mendapatkan cakupan cabang, dapat menemukan kode mati, tetapi tidak dapat menemukan bug yang berasal dari kondisi yang salah .
- Dalam cakupan beberapa kondisi Minimal , setiap kondisi atomik dan komposit dulunya benar dan salah. Itu masih menyiratkan cakupan cabang penuh. Ini adalah bagian dari cakupan beberapa kondisi, tetapi membutuhkan lebih sedikit kasus uji.
Saat membuat input uji menggunakan cakupan kondisi, maka hubungan arus pendek harus diperhitungkan. Sebagai contoh,
function foo(A, B) {
if (A && B) x()
else y()
}
kebutuhan untuk diuji dengan foo(false, whatever)
, foo(true, false)
, dan foo(true, true)
untuk cakupan kondisi beberapa minim penuh.
Jika Anda memiliki objek yang dapat berada dalam beberapa status, maka pengujian semua transisi status yang dianalogikan untuk mengontrol aliran tampaknya masuk akal.
Ada beberapa metrik cakupan yang lebih kompleks, tetapi umumnya mirip dengan metrik yang disajikan di sini.
Ini adalah metode pengujian kotak putih , dan sebagian dapat diotomatisasi. Perhatikan bahwa rangkaian uji unit harus bertujuan untuk memiliki cakupan kode tinggi dengan metrik apa pun yang dipilih, tetapi 100% tidak selalu mungkin. Terutama sulit untuk menguji penanganan pengecualian, di mana kesalahan harus disuntikkan ke lokasi tertentu.
Tes fungsional
Lalu ada tes fungsional yang menyatakan bahwa kode mematuhi spesifikasi dengan melihat implementasi sebagai kotak hitam. Tes semacam itu berguna untuk pengujian unit dan pengujian integrasi. Karena tidak mungkin untuk menguji dengan semua data input yang mungkin (misalnya menguji panjang string dengan semua string yang mungkin), maka berguna untuk mengelompokkan input (dan output) ke dalam kelas yang setara - jika length("foo")
benar, foo("bar")
kemungkinan akan bekerja juga. Untuk setiap kemungkinan kombinasi antara input dan output kelas kesetaraan, setidaknya satu input representatif dipilih dan diuji.
Satu juga harus menguji
- kasus tepi
length("")
, foo("x")
, length(longer_than_INT_MAX)
,
- nilai-nilai yang diizinkan oleh bahasa, tetapi tidak oleh kontrak fungsi
length(null)
, dan
- kemungkinan data sampah
length("null byte in \x00 the middle")
...
Dengan angka, ini berarti pengujian 0, ±1, ±x, MAX, MIN, ±∞, NaN
, dan dengan perbandingan titik mengambang menguji dua pelampung yang berdekatan. Sebagai tambahan lain, nilai tes acak dapat diambil dari kelas ekivalensi. Untuk memudahkan debugging, ada baiknya merekam seed yang digunakan ...
Tes non-fungsional: Load test, Stress test
Sepotong perangkat lunak memiliki persyaratan non-fungsional, yang harus diuji juga. Ini termasuk pengujian pada batas yang ditentukan (tes beban), dan di luar batas itu (tes stres). Untuk gim komputer, ini bisa berarti jumlah minimum frame per detik dalam uji beban. Sebuah situs web mungkin diuji stres untuk mengamati waktu respons ketika pengunjung dua kali lebih banyak dari yang diperkirakan menghancurkan server. Tes semacam itu tidak hanya relevan untuk seluruh sistem tetapi juga untuk entitas tunggal - bagaimana tabel hash terdegradasi dengan sejuta entri?
Jenis pengujian lainnya adalah pengujian sistem keseluruhan di mana skenario disimulasikan, atau tes penerimaan untuk membuktikan bahwa kontrak pengembangan telah dipenuhi.
Metode non-pengujian
Ulasan
Ada teknik non-pengujian yang dapat digunakan untuk jaminan kualitas. Contohnya adalah penelusuran, tinjauan kode formal, atau pemrograman pasangan. Sementara beberapa bagian dapat diotomatisasi (misalnya dengan menggunakan linter), ini umumnya memakan waktu. Namun, tinjauan kode oleh programmer berpengalaman memiliki tingkat penemuan bug yang tinggi, dan sangat berharga selama desain, di mana tidak ada pengujian otomatis yang memungkinkan.
Ketika ulasan kode sangat bagus, mengapa kita masih menulis tes? Keuntungan besar dari test suites adalah mereka dapat berjalan (kebanyakan) secara otomatis, dan sangat berguna untuk tes regresi .
Verifikasi formal
Verifikasi formal berjalan dan membuktikan sifat-sifat tertentu dari kode. Verifikasi manual sebagian besar layak untuk bagian-bagian penting, kurang untuk seluruh program. Bukti menempatkan batas bawah pada kebenaran program. Bukti dapat diotomatisasi ke tingkat tertentu, misalnya melalui pemeriksa tipe statis.
Invarian tertentu dapat secara eksplisit diperiksa dengan menggunakan assert
pernyataan.
Semua teknik ini memiliki tempat masing-masing, dan saling melengkapi. TDD menulis tes fungsional di depan, tetapi tes dapat dinilai dengan metrik cakupan setelah kode diterapkan.
Menulis kode yang dapat diuji berarti menulis unit kode kecil yang dapat diuji secara terpisah (fungsi pembantu dengan rincian yang sesuai, prinsip tanggung jawab tunggal). Semakin sedikit argumen yang dilakukan masing-masing fungsi, semakin baik. Kode tersebut juga cocok untuk penyisipan objek tiruan, misalnya melalui injeksi ketergantungan.
double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }
yang saya pelajari dari guru matematika saya . Kode ini memiliki satu lubang , yang tidak dapat ditemukan secara otomatis hanya dari pengujian kotak hitam. Dalam Matematika tidak ada lubang seperti itu. Dalam kalkulus Anda diizinkan menutup lubang jika batas satu sisi sama.