Bagaimana cara menulis unit test "baik"?

61

Dipicu oleh utas ini , saya (sekali lagi) berpikir untuk akhirnya menggunakan unit test dalam proyek saya. Beberapa poster di sana mengatakan sesuatu seperti "Tes itu keren, jika itu tes yang bagus". Pertanyaan saya sekarang: Apa itu tes "baik"?

Dalam aplikasi saya, bagian utama sering kali adalah semacam analisis numerik, tergantung pada sejumlah besar data yang diamati, dan menghasilkan fungsi fit yang dapat digunakan untuk memodelkan data ini. Saya merasa sangat sulit untuk membuat tes untuk metode ini, karena jumlah input dan hasil yang mungkin terlalu besar untuk hanya menguji setiap kasus, dan metode itu sendiri seringkali agak gondrong dan tidak dapat dengan mudah di refactored tanpa mengorbankan kinerja. Saya terutama tertarik pada tes "baik" untuk metode semacam ini.

Jens
sumber
8
Setiap unit test yang baik hanya akan menguji satu hal - jika gagal Anda harus tahu persis apa yang salah.
gablin
2
Ketika memiliki sejumlah besar data, hal yang baik adalah menulis tes generik yang dapat mengambil file data sebagai input. File data biasanya harus berisi input dan hasil yang diharapkan. Dengan kerangka kerja uji xunit, Anda dapat membuat kasus uji dengan cepat - satu untuk setiap sampel data.
froderik
2
@ gablin "Jika gagal, Anda harus tahu persis apa yang salah" akan menyarankan bahwa tes dengan beberapa kemungkinan penyebab kegagalan tidak apa-apa, selama Anda dapat menentukan penyebab dari hasil tes ...?
user253751
Sepertinya tidak ada yang menyebutkan bahwa unit test dapat menguji berapa lama waktu operasi. Anda dapat memperbaiki kode Anda dengan mempertimbangkan kinerja, memastikan bahwa tes unit memberi tahu Anda apakah itu lulus atau gagal berdasarkan waktu serta hasil.
CJ Dennis

Jawaban:

52

Seni Pengujian Unit memiliki pendapat berikut tentang pengujian unit:

Tes unit harus memiliki properti berikut:

  • Itu harus otomatis dan berulang.
  • Seharusnya mudah diimplementasikan.
  • Setelah ditulis, harus tetap untuk digunakan di masa depan.
  • Siapa pun harus dapat menjalankannya.
  • Ini harus dijalankan dengan menekan sebuah tombol.
  • Itu harus berjalan cepat.

dan kemudian menambahkannya harus sepenuhnya otomatis, dapat dipercaya, dapat dibaca, dan dipelihara.

Saya sangat merekomendasikan membaca buku ini jika Anda belum melakukannya.

Menurut pendapat saya, semua ini sangat penting, tetapi tiga yang terakhir (dapat dipercaya, dapat dibaca, dan dipelihara) terutama, seolah-olah tes Anda memiliki tiga properti ini maka kode Anda biasanya memilikinya juga.

Andy Lowry
sumber
1
+1 untuk daftar komprehensif yang ditargetkan pada pengujian unit (bukan pengujian integrasi atau fungsional)
Gary Rowe
1
+1 untuk tautan. Bahan menarik dapat ditemukan di sana.
Joris Meys
1
"Lari cepat" memiliki implikasi besar. Ini adalah salah satu alasan mengapa unit test harus dijalankan secara terpisah, jauh dari sumber daya eksternal seperti database, sistem file, layanan web, dll. Ini, pada gilirannya, mengarah pada ejekan / bertopik.
Michael Easter
1
ketika dikatakan It should run at the push of a button, apakah itu berarti bahwa tes unit tidak boleh mengharuskan kontainer (server aplikasi) berjalan (untuk unit yang sedang diuji) atau koneksi sumber daya (seperti DB, layanan web eksternal dll)? Saya hanya bingung bagian mana dari suatu aplikasi yang harus diuji unit dan mana yang tidak. Saya telah diberitahu bahwa tes unit tidak harus bergantung pada koneksi DB dan menjalankan wadah dan mungkin menggunakan mockup.
amfibi
42

Tes unit yang baik tidak mencerminkan fungsi yang sedang diuji.

Sebagai contoh yang sangat sederhana, anggap Anda memiliki fungsi yang mengembalikan rata-rata dua int. Tes yang paling komprehensif akan memanggil fungsi dan memeriksa apakah hasilnya ternyata rata-rata. Ini sama sekali tidak masuk akal: Anda sedang meniru (mereplikasi) fungsionalitas yang Anda uji. Jika Anda membuat kesalahan dalam fungsi utama, Anda akan membuat kesalahan yang sama dalam ujian.

Dengan kata lain, jika Anda mereplikasi fungsi utama dalam tes unit, itu adalah tanda bahwa Anda membuang-buang waktu.

Mojuba
sumber
21
+1 Apa yang akan Anda lakukan dalam kasus ini adalah pengujian dengan argumen yang dikodekan dengan keras dan periksa jawaban Anda yang diketahui.
Michael K
Saya pernah melihat bau itu sebelumnya.
Paul Butcher
Bisakah Anda memberikan contoh pengujian unit yang baik untuk fungsi yang mengembalikan rata-rata?
VLAS
2
@VLAS menguji nilai yang telah ditentukan, mis. Pastikan avg (1, 3) == 2, juga yang lebih penting memeriksa kasus tepi, seperti INT_MAX, nol, nilai negatif, dll. Jika bug ditemukan dan diperbaiki dalam fungsi, tambahkan yang lain tes untuk memastikan bug ini tidak pernah diperkenalkan kembali.
mojuba
Menarik. Bagaimana Anda mengusulkan untuk mendapatkan jawaban yang benar untuk input tes tersebut, dan tidak berpotensi membuat kesalahan yang sama dengan kode yang diuji?
Timo
10

Tes unit yang baik pada dasarnya adalah spesifikasi dalam bentuk runnable:

  1. menggambarkan perilaku kode yang sesuai dengan kasus penggunaan
  2. mencakup kasus sudut teknis (apa yang terjadi jika nol dilewatkan) - jika tes tidak ada untuk kasus sudut, perilaku tidak ditentukan.
  3. istirahat jika kode yang diuji berubah dari spesifikasi

Saya telah menemukan Test-Driven-Development sangat cocok untuk rutinitas perpustakaan ketika Anda pada dasarnya menulis API terlebih dahulu, dan MAKA implementasi yang sebenarnya.


sumber
7

untuk TDD, fitur pengujian "baik" menguji yang diinginkan pelanggan ; fitur tidak harus sesuai dengan fungsi, dan skenario pengujian tidak boleh dibuat oleh pengembang dalam ruang hampa

dalam kasus Anda - saya menduga - 'fitur' adalah bahwa fungsi fit memodelkan data input dalam toleransi kesalahan tertentu. Karena saya tidak tahu apa yang sebenarnya Anda lakukan, saya mengarang sesuatu; semoga itu analgous.

Contoh cerita:

Sebagai [Pilot X-Wing] saya ingin [tidak lebih dari 0,0001% kesalahan pas] sehingga [komputer penargetan dapat mengenai port knalpot Death Star saat bergerak dengan kecepatan penuh melalui kotak ngarai]

Jadi, Anda berbicara dengan pilot (dan ke komputer penargetan, jika hidup). Pertama Anda berbicara tentang apa yang 'normal', kemudian berbicara tentang yang tidak normal. Anda mencari tahu apa yang sebenarnya penting dalam skenario ini, apa yang umum, apa yang tidak mungkin, dan apa yang hanya mungkin.

Katakanlah bahwa biasanya Anda akan memiliki jendela setengah detik lebih dari tujuh saluran data telemetri: kecepatan, pitch, roll, menguap, vektor target, ukuran target, dan kecepatan target, dan bahwa nilai-nilai ini akan konstan atau berubah secara linear. Abnormal Anda mungkin memiliki lebih sedikit saluran dan / atau nilainya dapat berubah dengan cepat. Jadi, bersama-sama Anda membuat beberapa tes seperti:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Sekarang, Anda mungkin telah memperhatikan bahwa tidak ada skenario untuk situasi tertentu yang dijelaskan dalam cerita. Ternyata, setelah berbicara dengan pelanggan dan pemangku kepentingan lainnya, tujuan dalam cerita aslinya hanyalah contoh hipotetis. Tes nyata keluar dari diskusi berikutnya. Ini bisa terjadi. Ceritanya harus ditulis ulang, tetapi tidak harus [karena ceritanya hanyalah pengganti untuk percakapan dengan pelanggan].

Steven A. Lowe
sumber
5

Buat tes untuk kasing sudut, seperti set tes yang hanya berisi jumlah input minimum (mungkin 1 atau 0) dan beberapa kasing standar. Tes-tes unit itu bukan pengganti untuk tes penerimaan menyeluruh, juga tidak seharusnya demikian.

pengguna281377
sumber
5

Saya telah melihat banyak kasus di mana orang menginvestasikan sejumlah besar upaya menulis tes untuk kode yang jarang dimasukkan, dan tidak menulis tes untuk kode yang sering dimasukkan.

Sebelum duduk untuk menulis tes apa pun, Anda harus melihat semacam grafik panggilan, untuk memastikan Anda merencanakan cakupan yang memadai.

Selain itu, saya tidak percaya pada tes menulis hanya untuk mengatakan "Ya, kami menguji itu". Jika saya menggunakan pustaka yang dijatuhkan dan akan tetap tidak berubah, saya tidak akan menyia-nyiakan tes menulis sehari untuk memastikan jeroan API yang tidak akan pernah berubah berfungsi seperti yang diharapkan, bahkan jika bagian tertentu dari skor itu tinggi pada grafik panggilan. Tes yang menggunakan perpustakaan kata (kode saya sendiri) menunjukkan ini.

Pos Tim
sumber
tetapi bagaimana di kemudian hari ketika perpustakaan memiliki versi yang lebih baru dengan perbaikan bug?
@ Thorbjørn Ravn Andersen - Tergantung pada perpustakaan, apa yang berubah dan proses pengujian mereka sendiri. Saya tidak akan menulis tes untuk kode yang saya tahu berfungsi ketika saya menjatuhkannya di tempat, dan tidak pernah menyentuh. Jadi, jika bekerja setelah memperbarui, tidak masuk akal :) Tentu saja ada pengecualian.
Tim Post
jika Anda bergantung pada perpustakaan Anda, yang paling dapat Anda lakukan adalah menulis tes yang menunjukkan apa yang Anda harapkan kata perpustakaan untuk benar-benar melakukan ,
... dan jika itu berubah, tes pada hal-hal yang mengkonsumsi perpustakaan kata ... tl; dr; Saya tidak perlu menguji jeroan kode pihak ketiga. Jawabannya diperbarui untuk kejelasan.
Tim Post
4

Tidak begitu TDD, tetapi setelah Anda masuk ke QA Anda dapat meningkatkan tes Anda dengan mengatur kasus uji untuk mereproduksi bug yang muncul selama proses QA. Ini bisa sangat berharga ketika Anda pergi ke dukungan jangka panjang dan Anda mulai pergi ke tempat di mana Anda berisiko orang secara tidak sengaja memperkenalkan kembali bug lama. Memiliki tes untuk menangkap yang sangat berharga.

glenatron
sumber
3

Saya mencoba agar setiap tes hanya menguji satu hal. Saya mencoba memberi setiap tes nama seperti shouldDoSomething (). Saya mencoba menguji perilaku, bukan implementasi. Saya hanya menguji metode publik.

Saya biasanya memiliki satu atau beberapa tes untuk sukses, dan kemudian mungkin beberapa tes untuk kegagalan, per metode publik.

Saya sering menggunakan mock-up. Mock-framework yang baik mungkin akan sangat membantu, seperti PowerMock. Meskipun saya belum menggunakannya.

Jika kelas A menggunakan kelas B lain, saya akan menambahkan antarmuka, X, sehingga A tidak menggunakan B secara langsung. Lalu saya akan membuat mock-up XMockup dan menggunakannya sebagai pengganti B dalam pengujian saya. Ini benar-benar membantu mempercepat pelaksanaan pengujian, mengurangi kompleksitas pengujian, dan juga mengurangi jumlah tes yang saya tulis untuk A karena saya tidak harus mengatasi kekhasan B. Saya dapat misalnya menguji bahwa A memanggil X.someMethod () alih-alih efek samping dari memanggil B.someMethod ().

Pastikan kode pengujian Anda juga bersih.

Saat menggunakan API, seperti lapisan basis data, saya akan mengejeknya dan memungkinkan mock-up untuk melemparkan pengecualian di setiap peluang yang ada pada perintah. Saya kemudian menjalankan tes satu tanpa melempar, dan dalam satu lingkaran, setiap kali melempar pengecualian pada kesempatan berikutnya sampai tes berhasil lagi. Agak seperti tes memori yang tersedia untuk Symbian.

Roger CS Wernersson
sumber
2

Saya melihat bahwa Andry Lowry telah memposting metrik pengujian unit Roy Osherove; tetapi tampaknya tidak ada yang memberikan set (gratis) yang diberikan Paman Bob dalam Kode Bersih (132-133). Dia menggunakan akronim PERTAMA (di sini dengan rangkuman saya):

  • Cepat (mereka harus berlari cepat, jadi orang tidak akan keberatan menjalankannya)
  • Independen (tes tidak boleh melakukan setup atau teardown satu sama lain)
  • Ulangi (harus dijalankan di semua lingkungan / platform)
  • Validasi sendiri (sepenuhnya otomatis; output harus berupa "lulus" atau "gagal", bukan file log)
  • Tepat waktu (kapan harus menulisnya — tepat sebelum menulis kode produksi yang mereka uji)
Kazark
sumber