Baru dalam pengujian unit, bagaimana menulis tes hebat? [Tutup]

267

Saya cukup baru di dunia pengujian unit, dan saya baru saja memutuskan untuk menambahkan cakupan pengujian untuk aplikasi saya yang ada minggu ini.

Ini adalah tugas besar, sebagian besar karena jumlah kelas untuk diuji tetapi juga karena tes menulis adalah hal baru bagi saya.

Saya sudah menulis tes untuk banyak kelas, tapi sekarang saya bertanya-tanya apakah saya melakukannya dengan benar.

Ketika saya menulis tes untuk suatu metode, saya memiliki perasaan menulis ulang untuk kedua kalinya apa yang sudah saya tulis dalam metode itu sendiri.
Tes saya sepertinya sangat terikat pada metode (menguji semua codepath, mengharapkan beberapa metode dalam disebut beberapa kali, dengan argumen tertentu), sehingga jika saya pernah refactor metode, tes akan gagal bahkan jika perilaku akhir metode ini tidak berubah.

Ini hanya perasaan, dan seperti yang dikatakan sebelumnya, saya tidak memiliki pengalaman pengujian. Jika beberapa penguji yang lebih berpengalaman di luar sana bisa memberi saya saran tentang cara menulis tes hebat untuk aplikasi yang ada, itu akan sangat dihargai.

Sunting: Saya ingin mengucapkan terima kasih kepada Stack Overflow, saya mendapat masukan yang bagus dalam waktu kurang dari 15 menit yang menjawab lebih banyak jam membaca online yang baru saya lakukan.

pixelastic
sumber
1
Ini adalah buku terbaik untuk pengujian unit: manning.com/osherove Ini menjelaskan semua praktik terbaik, lakukan, dan jangan lakukan untuk pengujian unit.
Ervi B
Satu hal yang tidak diperhatikan semua jawaban ini adalah bahwa pengujian unit seperti dokumentasi. Ergo, jika Anda menulis fungsi, Anda akan mendokumentasikan niatnya, dengan menjelaskan input dan outputnya (dan, mungkin, efek samping). Tes unit dimaksudkan untuk memverifikasi ini, lalu. Dan jika Anda (atau orang lain) kemudian membuat perubahan pada kode, dokumen harus menjelaskan batas-batas perubahan apa yang dapat dibuat, dan unit tes memastikan batas-batas itu dipelihara.
Thomas Tempelmann

Jawaban:

187

Tes saya sepertinya sangat terikat pada metode (menguji semua codepath, mengharapkan beberapa metode dalam disebut beberapa kali, dengan argumen tertentu), sehingga jika saya pernah refactor metode, tes akan gagal bahkan jika perilaku akhir metode ini tidak berubah.

Saya pikir Anda salah melakukannya.

Tes unit harus:

  • uji satu metode
  • berikan beberapa argumen spesifik untuk metode itu
  • menguji bahwa hasilnya seperti yang diharapkan

Seharusnya tidak melihat ke dalam metode untuk melihat apa yang dilakukannya, jadi mengubah internal seharusnya tidak menyebabkan tes gagal. Anda tidak boleh langsung menguji bahwa metode pribadi sedang dipanggil. Jika Anda tertarik untuk mengetahui apakah kode pribadi Anda sedang diuji, maka gunakan alat cakupan kode. Tapi jangan terobsesi dengan ini: cakupan 100% bukan persyaratan.

Jika metode Anda memanggil metode publik di kelas lain, dan panggilan ini dijamin oleh antarmuka Anda, maka Anda dapat menguji apakah panggilan ini dilakukan dengan menggunakan kerangka kerja mengejek.

Anda tidak boleh menggunakan metode itu sendiri (atau kode internal apa pun yang digunakannya) untuk menghasilkan hasil yang diharapkan secara dinamis. Hasil yang diharapkan harus dimasukkan dalam hard-coded ke dalam test case Anda sehingga tidak berubah ketika implementasi berubah. Berikut adalah contoh sederhana tentang apa yang harus dilakukan tes unit:

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;
    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

Perhatikan bahwa bagaimana hasil dihitung tidak dicentang - hanya saja hasilnya benar. Terus tambahkan lebih banyak kasus uji sederhana seperti di atas sampai Anda telah membahas sebanyak mungkin skenario. Gunakan alat cakupan kode Anda untuk melihat apakah Anda melewatkan jalur menarik.

Mark Byers
sumber
13
Terima kasih banyak, jawaban Anda lebih lengkap. Saya sekarang lebih mengerti untuk apa benda tiruan sebenarnya: Saya tidak perlu menegaskan setiap panggilan ke metode lain, hanya yang relevan. Saya juga tidak perlu tahu BAGAIMANA melakukan sesuatu, tetapi mereka melakukannya dengan benar.
pixelastic
2
Dengan hormat saya pikir Anda melakukan kesalahan. Tes unit adalah tentang aliran eksekusi kode (pengujian kotak putih). Pengujian black box (apa yang Anda sarankan) biasanya teknik yang digunakan dalam pengujian fungsional (pengujian sistem dan integrasi).
Wes
1
"Tes unit harus menguji satu metode" Aku sebenarnya tidak setuju. Tes unit harus menguji satu konsep logis. Meskipun itu sering direpresentasikan sebagai satu metode, itu tidak selalu terjadi
robertmain
35

Untuk pengujian unit, saya menemukan Test Driven (tes pertama, kode kedua) dan kode pertama, tes kedua sangat berguna.

Alih-alih menulis kode, maka tes menulis. Tulis kode lalu lihat apa yang harus Anda pikirkan. Pikirkan tentang semua kegunaan yang dimaksudkan dan kemudian tulis tes untuk masing-masing. Saya menemukan tes menulis lebih cepat tetapi lebih terlibat daripada pengkodean itu sendiri. Tes harus menguji niat. Juga pikirkan tentang niat Anda akhirnya menemukan kasus sudut dalam fase penulisan tes. Dan tentu saja saat menulis tes Anda mungkin menemukan salah satu dari beberapa kegunaan menyebabkan bug (sesuatu yang sering saya temukan, dan saya sangat senang bug ini tidak merusak data dan tidak dicentang).

Namun pengujian hampir seperti pengkodean dua kali. Sebenarnya saya punya aplikasi di mana ada lebih banyak kode uji (kuantitas) daripada kode aplikasi. Salah satu contoh adalah mesin negara yang sangat kompleks. Saya harus memastikan bahwa setelah menambahkan lebih banyak logika untuk itu, semuanya selalu bekerja pada semua kasus penggunaan sebelumnya. Dan karena kasus-kasus itu cukup sulit untuk diikuti dengan melihat kode, saya akhirnya memiliki test suite yang baik untuk mesin ini sehingga saya yakin bahwa itu tidak akan impas setelah membuat perubahan, dan tes menyelamatkan pantat saya beberapa kali . Dan karena pengguna atau penguji menemukan bug dengan kasus aliran atau sudut tidak dihitung, tebak apa, ditambahkan ke tes dan tidak pernah terjadi lagi. Ini benar-benar memberi pengguna kepercayaan diri dalam pekerjaan saya selain membuat semuanya menjadi sangat stabil. Dan ketika itu harus ditulis ulang untuk alasan kinerja, coba tebak,

Semua contoh sederhana seperti function square(number)hebat dan semuanya, dan mungkin adalah kandidat yang buruk untuk menghabiskan banyak pengujian waktu. Yang melakukan logika bisnis penting, di situlah pengujian itu penting. Uji persyaratannya. Jangan hanya menguji pipa ledeng. Jika persyaratannya berubah maka coba tebak, tes juga harus.

Pengujian tidak boleh benar-benar menguji fungsi yang dipanggil bar fungsi 3 kali. Itu salah. Periksa apakah hasil dan efek sampingnya benar, bukan mekanika dalam.

Dmitriy Likhten
sumber
2
Jawaban yang bagus, memberi saya kepercayaan diri bahwa menulis tes setelah kode masih dapat bermanfaat dan mungkin.
pixelastic
2
Contoh terbaru yang sempurna. Saya memiliki fungsi yang sangat sederhana. Pass it true, itu melakukan satu hal, salah itu melakukan yang lain. SANGAT SEDERHANA. Sudah seperti 4 tes memeriksa untuk memastikan fungsi melakukan apa yang ingin dilakukan. Saya mengubah perilaku sedikit. Jalankan tes, POW masalah. Lucunya ketika menggunakan aplikasi masalah tidak terwujud, hanya dalam kasus kompleks yang terjadi. Kasus tes menemukannya dan saya menyelamatkan diri dari sakit kepala berjam-jam.
Dmitriy Likhten
"Tes harus menguji niatnya." Saya pikir ini merangkumnya, bahwa Anda harus melalui penggunaan kode yang dimaksudkan dan memastikan bahwa kode tersebut dapat mengakomodasi mereka. Ini juga menunjuk pada ruang lingkup dari apa yang seharusnya diuji oleh tes dan gagasan bahwa, ketika Anda membuat perubahan kode, pada saat ini Anda mungkin tidak mempertimbangkan di telepon bagaimana perubahan itu berdampak pada semua penggunaan kode yang ditentukan - tes membela terhadap perubahan yang tidak memenuhi semua kasus penggunaan yang dimaksudkan.
Greenstick
18

Perlu dicatat bahwa tes unit pas-retro ke dalam kode yang ada jauh lebih sulit daripada mendorong penciptaan kode itu dengan tes di tempat pertama. Itu salah satu pertanyaan besar dalam berurusan dengan aplikasi warisan ... bagaimana menguji unit? Ini telah ditanyakan berkali-kali sebelumnya (jadi Anda mungkin ditutup sebagai pertanyaan dupe), dan orang-orang biasanya berakhir di sini:

Memindahkan kode yang ada ke Test Driven Development

Saya mendukung rekomendasi buku jawaban yang diterima, tetapi di luar itu ada lebih banyak informasi yang terkait dengan jawaban di sana.

David
sumber
3
Jika Anda menulis tes pertama atau kedua, itu baik-baik saja, tetapi ketika menulis tes Anda memastikan kode Anda dapat diuji sehingga Anda BISA menulis tes. Anda akhirnya berpikir "bagaimana saya bisa menguji ini" sering yang dengan sendirinya menyebabkan kode yang lebih baik untuk ditulis. Perkuatan uji kasus selalu merupakan larangan besar. Sangat keras. Ini bukan masalah waktu, itu masalah kuantitas dan testabilitas. Saya tidak bisa mendatangi bos saya sekarang dan mengatakan saya ingin menulis kasus uji untuk lebih dari seribu tabel dan penggunaan kami, terlalu banyak sekarang, akan memakan waktu satu tahun, dan beberapa logika / keputusan dilupakan. Jadi jangan menundanya terlalu lama: P
Dmitriy Likhten
2
Agaknya jawaban yang diterima telah berubah. Ada jawaban dari Linx yang merekomendasikan Seni pengujian unit oleh Roy Osherove, manning.com/osherove
thelem
15

Jangan menulis tes untuk mendapatkan cakupan penuh dari kode Anda. Tulis tes yang menjamin kebutuhan Anda. Anda mungkin menemukan codepath yang tidak perlu. Sebaliknya, jika perlu, mereka ada di sana untuk memenuhi semacam persyaratan; temukan apa adanya dan uji persyaratannya (bukan jalannya).

Pertahankan tes Anda kecil: satu tes per persyaratan.

Kemudian, ketika Anda perlu melakukan perubahan (atau menulis kode baru), cobalah menulis satu tes terlebih dahulu. Hanya satu. Maka Anda akan telah mengambil langkah pertama dalam pengembangan berbasis tes.

Jon Reid
sumber
Terima kasih, masuk akal untuk hanya melakukan tes kecil untuk persyaratan kecil, satu per satu. Pelajaran yang dipetik.
pixelastic
13

Pengujian unit adalah tentang output yang Anda dapatkan dari fungsi / metode / aplikasi. Sama sekali tidak masalah bagaimana hasilnya dihasilkan, itu hanya masalah bahwa itu benar. Karena itu, pendekatan Anda menghitung panggilan ke metode batin dan semacamnya salah. Apa yang saya cenderung lakukan adalah duduk dan menulis metode apa yang harus dikembalikan mengingat nilai input tertentu atau lingkungan tertentu, kemudian menulis tes yang membandingkan nilai aktual yang dikembalikan dengan apa yang saya hasilkan.

fresskoma
sumber
Terima kasih! Saya punya perasaan saya melakukan kesalahan, tetapi memiliki seseorang yang sebenarnya memberi tahu saya lebih baik.
pixelastic
8

Cobalah menulis Tes Unit sebelum menulis metode yang akan diuji.

Itu pasti akan memaksa Anda untuk berpikir sedikit berbeda tentang bagaimana hal-hal dilakukan. Anda tidak akan tahu bagaimana metode ini akan bekerja, hanya apa yang seharusnya dilakukan.

Anda harus selalu menguji hasil metode, bukan bagaimana metode mendapatkan hasil tersebut.

Justin Niessner
sumber
Ya, saya ingin sekali bisa melakukan itu, kecuali metodenya sudah ditulis. Saya hanya ingin menguji mereka. Saya akan menulis tes sebelum metode di masa depan, tho.
pixelastic
2
@pixelastic berpura-pura bahwa metode belum ditulis?
berkomitmenandroider
4

tes seharusnya meningkatkan rawatan. Jika Anda mengubah metode dan istirahat tes itu bisa menjadi hal yang baik. Di sisi lain, jika Anda melihat metode Anda sebagai kotak hitam maka tidak masalah apa yang ada di dalam metode tersebut. Faktanya adalah Anda perlu mengejek hal-hal untuk beberapa tes, dan dalam kasus tersebut Anda benar-benar tidak dapat memperlakukan metode sebagai kotak hitam. Satu-satunya hal yang dapat Anda lakukan adalah menulis tes integrasi - Anda memuat instance instantiated sepenuhnya dari layanan yang sedang diuji dan meminta dia melakukan hal seperti itu akan berjalan di aplikasi Anda. Maka Anda bisa memperlakukannya sebagai kotak hitam.

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

Ini karena Anda menulis tes setelah Anda menulis kode. Jika Anda melakukannya sebaliknya (menulis tes pertama) itu tidak akan terasa seperti ini.

hvgotcodes
sumber
Terima kasih untuk contoh kotak hitam, saya belum berpikir seperti itu. Saya berharap saya menemukan pengujian unit sebelumnya, tapi sayangnya, itu tidak terjadi dan saya terjebak dengan aplikasi lawas untuk menambahkan tes. Apakah tidak ada cara untuk menambahkan tes ke proyek yang sudah ada tanpa mereka merasa rusak?
pixelastic
1
Tes menulis setelah berbeda dari tes menulis sebelumnya, jadi Anda terjebak dengannya. namun, yang dapat Anda lakukan adalah mengatur tes agar gagal terlebih dahulu, kemudian lulus dengan menempatkan kelas Anda dalam ujian .... lakukan sesuatu seperti itu, masukkan instance Anda dalam pengujian setelah tes awalnya gagal. Hal yang sama dengan mengolok-olok - awalnya mock tidak memiliki harapan, dan akan gagal karena metode yang diuji akan melakukan sesuatu dengan mock, kemudian melakukan tes lulus. Saya tidak akan terkejut jika Anda menemukan banyak bug dengan cara ini.
hvgotcodes
juga, sangat spesifik dengan harapan Anda. Jangan hanya menegaskan bahwa tes mengembalikan suatu objek, uji bahwa objek memiliki berbagai nilai di atasnya. Uji bahwa ketika suatu nilai seharusnya nol, maka itu adalah. Anda juga dapat memecahnya sedikit dengan melakukan beberapa refactoring yang ingin Anda lakukan, setelah Anda menambahkan beberapa tes.
hvgotcodes