Saya mencoba memahami unit testing.
Katakanlah kita memiliki dadu yang dapat memiliki jumlah sisi standar sama dengan 6 (tetapi dapat berupa 4, 5 sisi, dll.):
import random
class Die():
def __init__(self, sides=6):
self._sides = sides
def roll(self):
return random.randint(1, self._sides)
Apakah yang berikut ini akan menjadi tes unit yang valid / berguna?
- uji gulungan dalam kisaran 1-6 untuk cetakan 6 sisi
- uji gulungan 0 untuk dadu 6 sisi
- uji gulungan 7 untuk dadu 6 sisi
- uji gulungan dalam kisaran 1-3 untuk dadu 3 sisi
- uji gulungan 0 untuk dadu 3 sisi
- uji gulungan 4 untuk dadu 3 sisi
Saya hanya berpikir bahwa ini adalah buang-buang waktu karena modul acak telah ada cukup lama tetapi kemudian saya pikir jika modul acak diperbarui (katakanlah saya memperbarui versi Python saya) maka setidaknya saya dibahas.
Juga, apakah saya bahkan perlu menguji variasi lain dari gulungan mati misalnya 3 dalam hal ini, atau apakah baik untuk mencakup keadaan cetakan lain yang sudah diinisialisasi?
python
unit-testing
tdd
Cybran
sumber
sumber
Jawaban:
Anda benar, tes Anda seharusnya tidak memverifikasi bahwa
random
modul melakukan tugasnya; unittest seharusnya hanya menguji kelas itu sendiri, bukan bagaimana ia berinteraksi dengan kode lain (yang harus diuji secara terpisah).Tentu saja sangat mungkin kode Anda
random.randint()
salah; atau Andarandom.randrange(1, self._sides)
malah menelepon dan mati Anda tidak pernah melempar nilai tertinggi, tetapi itu akan menjadi jenis bug yang berbeda, bukan bug yang bisa Anda tangkap dengan yang paling sedikit. Jika demikian,die
unit Anda berfungsi seperti yang dirancang, tetapi desainnya sendiri cacat.Dalam hal ini, saya akan menggunakan mengejek untuk menggantikan yang
randint()
fungsi, dan hanya memverifikasi bahwa telah disebut dengan benar. Python 3.3 dan yang lebih baru hadir denganunittest.mock
modul untuk menangani jenis pengujian ini, tetapi Anda dapat menginstalmock
paket eksternal pada versi yang lebih lama untuk mendapatkan fungsionalitas yang sama persisDengan mengejek, tes Anda sekarang sangat sederhana; hanya ada 2 kasus, sungguh. Kasing default untuk dadu 6 sisi, dan kasing sisi khusus.
Ada cara lain untuk mengganti sementara
randint()
fungsi dalam namespace globalDie
, tetapimock
modul membuatnya lebih mudah. The@mock.patch
dekorator di sini berlaku untuk semua metode pengujian dalam kasus uji; setiap metode pengujian dilewatkan argumen tambahan,random.randint()
fungsi yang diolok-olok , sehingga kita dapat menguji terhadap tiruan untuk melihat apakah itu memang telah dipanggil dengan benar. Thereturn_value
Argumen menspesifikasikan apa yang kembali dari mock ketika itu disebut, sehingga kami dapat memverifikasi bahwadie.roll()
metode memang mengembalikan 'acak' hasil kepada kami.Saya telah menggunakan praktik terbaik unittesting Python lain di sini: impor kelas yang sedang diuji sebagai bagian dari tes. The
_make_one
Metode melakukan impor dan Instansiasi bekerja dalam tes , sehingga tes modul masih akan memuat bahkan jika Anda membuat kesalahan sintaks atau kesalahan lain yang akan mencegah modul asli untuk impor.Dengan cara ini, jika Anda membuat kesalahan dalam kode modul itu sendiri, tes akan tetap dijalankan; mereka hanya akan gagal, memberi tahu Anda tentang kesalahan dalam kode Anda.
Agar jelas, tes di atas adalah sederhana di ekstrem. Tujuannya di sini bukan untuk menguji yang
random.randint()
telah dipanggil dengan argumen yang tepat, misalnya. Sebaliknya, tujuannya adalah untuk menguji bahwa unit menghasilkan hasil yang tepat diberikan input tertentu, di mana input tersebut mencakup hasil unit lain yang tidak diuji. Dengan mengejekrandom.randint()
metode ini, Anda bisa mengendalikan hanya input lain ke kode Anda.Dalam tes dunia nyata , kode aktual dalam unit-under-test Anda akan menjadi lebih kompleks; hubungan dengan input yang diteruskan ke API dan bagaimana unit lain dipanggil dapat tetap menarik, dan mengejek akan memberi Anda akses ke hasil antara, serta memungkinkan Anda menetapkan nilai balik untuk panggilan tersebut.
Misalnya, dalam kode yang mengautentikasi pengguna terhadap layanan OAuth2 pihak ketiga (interaksi multi-tahap), Anda ingin menguji bahwa kode Anda meneruskan data yang benar ke layanan pihak ke-3 itu, dan memungkinkan Anda mengejek berbagai respons kesalahan yang berbeda yang Layanan pihak ketiga akan kembali, memungkinkan Anda mensimulasikan skenario yang berbeda tanpa harus membangun sendiri server OAuth2 lengkap. Di sini penting untuk menguji bahwa informasi dari respons pertama telah ditangani dengan benar dan telah diteruskan ke panggilan tahap kedua, jadi Anda ingin melihat bahwa layanan yang dipermainkan dipanggil dengan benar.
sumber
randint()
, bukan kode masukDie.roll()
.sentinel.die
contoh (objek sentinel juga dariunittest.mock
) dan kemudian memverifikasi bahwa itu adalah apa yang dikembalikan dari metode roll Anda. Ini sebenarnya hanya memungkinkan satu cara menerapkan metode yang diuji.sentinel.die
akan menjadi cara yang bagus untuk memastikannya.Jawaban Martijn adalah bagaimana Anda akan melakukannya jika Anda benar-benar ingin menjalankan tes yang menunjukkan bahwa Anda menelepon secara acak. Brandint. Namun, dengan risiko diberi tahu "itu tidak menjawab pertanyaan", saya merasa ini seharusnya tidak diuji unit sama sekali. Mengejek Randand tidak lagi pengujian kotak hitam - Anda secara khusus menunjukkan bahwa hal-hal tertentu sedang terjadi dalam implementasi . Pengujian black box bahkan bukan opsi - tidak ada tes yang dapat Anda lakukan yang akan membuktikan bahwa hasilnya tidak akan pernah kurang dari 1 atau lebih dari 6.
Bisakah kamu mengejek
randint
? Ya kamu bisa. Tapi apa yang kamu buktikan? Anda menyebutnya dengan argumen 1 dan sisi. Apa artinya itu ? Anda kembali ke titik awal - pada akhirnya Anda harus membuktikan - secara formal atau tidak formal - bahwa panggilanrandom.randint(1, sides)
dengan benar akan menerapkan roll dadu.Saya semua untuk pengujian unit. Mereka cek kewarasan fantastis dan mengekspos kehadiran bug. Namun, mereka tidak pernah dapat membuktikan ketidakhadiran mereka, dan ada hal-hal yang tidak dapat ditegaskan melalui pengujian sama sekali (misalnya bahwa fungsi tertentu tidak pernah melempar pengecualian atau selalu berakhir.) Dalam kasus khusus ini, saya merasa ada sangat sedikit Anda berdiri untuk menguji mendapatkan. Untuk perilaku yang deterministik, unit test masuk akal karena Anda benar-benar tahu apa jawaban yang Anda harapkan.
sumber
random.randint
disebut dengan1, sides
tidak berguna jika itu hal yang salah untuk dilakukan.random.randint()
akan mengembalikan nilai dalam rentang [1, sisi] dengan benar (inklusif), terserah pengembang Python untuk memastikan bahwarandom
unit bekerja dengan benar.random.randint()
berperilaku sepertirandom.randrange()
dan dengan demikian menyebutnyarandom.randint(1, sides + 1)
, maka Anda tetap saja tenggelamMemperbaiki benih acak. Untuk dadu bersisi 1, 2, 5, dan 12 sisi, pastikan bahwa beberapa ribu gulungan memberikan hasil termasuk 1 dan N, dan tidak termasuk 0 atau N + 1. Jika secara kebetulan Anda mendapatkan serangkaian hasil acak yang tidak tutup kisaran yang diharapkan, ganti dengan seed yang berbeda.
Alat mengejek itu keren, tetapi hanya karena memungkinkan Anda melakukan sesuatu, tidak berarti hal itu harus dilakukan. YAGNI berlaku untuk perlengkapan pengujian sebanyak fitur.
Jika Anda dapat dengan mudah menguji dengan dependensi yang tidak diolok-olok, Anda harus selalu melakukannya; dengan cara itu tes Anda akan difokuskan pada pengurangan jumlah cacat, tidak hanya meningkatkan jumlah tes. Kelebihan risiko mencibir menciptakan angka cakupan menyesatkan, yang pada gilirannya dapat menyebabkan menunda pengujian yang sebenarnya ke beberapa tahap selanjutnya Anda mungkin tidak pernah punya waktu untuk ...
sumber
Apa itu
Die
jika Anda memikirkannya? - tidak lebih dari pembungkusrandom
. Hal ini merangkumrandom.randint
dan relabels itu dalam hal kosakata aplikasi Anda sendiri:Die.Roll
.Saya tidak merasa relevan untuk menyisipkan lapisan abstraksi lain di antara
Die
danrandom
karenaDie
itu sendiri sudah merupakan lapisan tipuan antara aplikasi Anda dan platform.Jika Anda ingin hasil dadu kalengan, hanya mengejek
Die
, jangan mengejekrandom
.Secara umum, saya tidak menguji unit objek pembungkus saya yang berkomunikasi dengan sistem eksternal, saya menulis tes integrasi untuk mereka. Anda dapat menulis beberapa untuk
Die
tetapi seperti yang Anda tunjukkan, karena sifat acak dari objek yang mendasarinya, mereka tidak akan bermakna. Selain itu, tidak ada konfigurasi atau komunikasi jaringan yang terlibat di sini sehingga tidak banyak untuk diuji kecuali panggilan platform.=> Menimbang bahwa
Die
hanya beberapa baris kode yang sepele dan menambahkan sedikit atau tidak ada logika dibandingkan denganrandom
dirinya sendiri, saya akan melewatkan pengujian dalam contoh khusus itu.sumber
Menyemai generator angka acak dan memverifikasi hasil yang diharapkan BUKAN, sejauh yang saya bisa lihat, tes yang valid. Itu membuat asumsi BAGAIMANA dadu Anda bekerja secara internal, yang nakal-nakal. Pengembang python dapat mengubah generator angka acak, atau die (CATATAN: "dadu" adalah jamak, "die" adalah singular. Kecuali jika kelas Anda mengimplementasikan beberapa die rolls dalam satu panggilan, mungkin seharusnya disebut "die") bisa menggunakan generator nomor acak yang berbeda.
Demikian pula, mengejek fungsi acak mengasumsikan bahwa implementasi kelas bekerja persis seperti yang diharapkan. Mengapa ini tidak terjadi? Seseorang mungkin mengendalikan generator nomor acak python default, dan untuk menghindarinya, versi die Anda yang akan datang mungkin mengambil beberapa angka acak, atau angka acak yang lebih besar, untuk digabungkan dengan lebih banyak data acak. Skema yang sama digunakan oleh pembuat sistem operasi FreeBSD, ketika mereka menduga NSA merusak perangkat keras nomor acak generator yang dibangun ke dalam CPU.
Jika itu saya, saya akan menjalankan, katakanlah, 6000 gulungan, menghitungnya, dan memastikan bahwa setiap angka dari 1-6 digulung antara 500 dan 1500 kali. Saya juga akan memeriksa bahwa tidak ada angka di luar rentang yang dikembalikan. Saya mungkin juga memeriksa bahwa, untuk set kedua 6000 gulungan, ketika memesan [1..6] dalam urutan frekuensi, hasilnya berbeda (ini akan gagal sekali dari 720 run, jika jumlahnya acak!). Jika Anda ingin teliti, Anda mungkin menemukan frekuensi angka mengikuti 1, mengikuti 2, dll; tetapi pastikan ukuran sampel Anda cukup besar, dan Anda memiliki varians yang cukup. Manusia berharap angka acak memiliki pola lebih sedikit daripada yang sebenarnya.
Ulangi selama 12 sisi, dan 2 sisi mati (6 adalah yang paling sering digunakan, demikian juga yang paling diharapkan bagi siapa pun yang menulis kode ini).
Akhirnya, saya akan menguji untuk melihat apa yang terjadi dengan dadu 1 sisi, dadu 0 sisi, dadu sisi -1, dadu sisi 2,3, dadu sisi [1,2,3,4,5,6], dan mati "blah". Tentu saja, ini semua harus gagal; apakah mereka gagal dengan cara yang bermanfaat? Ini mungkin gagal pada saat penciptaan, bukan pada penguliran.
Atau, mungkin, Anda ingin menangani ini secara berbeda - mungkin membuat cetakan dengan [1,2,3,4,5,6] harus dapat diterima - dan mungkin "bla" juga; ini mungkin mati dengan 4 wajah, dan setiap wajah memiliki huruf di atasnya. Permainan "Boggle" muncul dalam pikiran, seperti halnya bola ajaib delapan.
Dan akhirnya, Anda mungkin ingin merenungkan ini: http://lh6.ggpht.com/-fAGXwbJbYRM/UJA_31ACOLI/AAAAAAAAAPg/2FxOWzo96KE/s1600-h/random%25255B3%25255D.jpg
sumber
Dengan risiko berenang melawan arus, saya memecahkan masalah yang tepat ini beberapa tahun yang lalu menggunakan metode yang sejauh ini tidak disebutkan.
Strategi saya hanyalah mengejek RNG dengan yang menghasilkan aliran nilai yang dapat diprediksi yang mencakup seluruh ruang. Jika (katakanlah) sisi = 6 dan RNG menghasilkan nilai dari 0 hingga 5 secara berurutan, saya dapat memprediksi bagaimana kelas saya harus berperilaku dan unit test sesuai.
Alasannya adalah bahwa ini menguji logika di kelas ini saja, dengan asumsi bahwa RNG pada akhirnya akan menghasilkan masing-masing nilai-nilai tersebut dan tanpa menguji RNG itu sendiri.
Sederhana, deterministik, dapat direproduksi, dan dapat menangkap bug. Saya akan menggunakan strategi yang sama lagi.
Pertanyaannya tidak menjabarkan apa tes yang seharusnya, hanya data apa yang mungkin digunakan untuk pengujian, mengingat adanya RNG. Saran saya hanyalah menguji secara menyeluruh dengan mengejek RNG. Pertanyaan tentang apa yang layak diuji tergantung pada informasi yang tidak disediakan dalam pertanyaan.
sumber
Tes yang Anda sarankan dalam pertanyaan Anda tidak mendeteksi penghitung aritmatika modular sebagai implementasi. Dan mereka tidak mendeteksi kesalahan implementasi umum dalam kode terkait distribusi probabilitas seperti
return 1 + (random.randint(1,maxint) % sides)
. Atau perubahan generator yang menghasilkan pola 2 dimensi.Jika Anda benar-benar ingin memverifikasi bahwa Anda menghasilkan angka acak yang terdistribusi secara merata, Anda perlu memeriksa berbagai properti yang sangat luas. Untuk melakukan pekerjaan yang cukup baik pada saat itu Anda bisa menjalankan http://www.phy.duke.edu/~rgb/General/dieharder.php pada nomor yang Anda hasilkan. Atau tulislah unit-test suite yang serupa rumitnya.
Itu bukan kesalahan unit-testing atau TDD, keacakan kebetulan menjadi properti yang sangat sulit untuk diverifikasi. Dan topik populer sebagai contoh.
sumber
Tes termudah dari die-roll adalah hanya mengulanginya beberapa ratus ribu kali, dan memvalidasi bahwa setiap hasil yang mungkin dipukul kira-kira (1 / jumlah sisi) kali. Dalam kasus dadu 6 sisi, Anda harus melihat setiap nilai yang mungkin mengenai sekitar 16,6% dari waktu. Jika ada yang mati lebih dari satu persen, maka Anda memiliki masalah.
Melakukannya dengan cara ini menghindari memungkinkan Anda untuk memperbaiki mekanisme yang mendasari menghasilkan angka acak dengan mudah, dan yang paling penting, tanpa mengubah tes.
sumber