Kapan menggunakan Mockito.verify ()?


201

Saya menulis jUnit test case untuk 3 keperluan:

  1. Untuk memastikan bahwa kode saya memenuhi semua fungsi yang diperlukan, di bawah semua (atau sebagian besar) kombinasi input / nilai.
  2. Untuk memastikan bahwa saya dapat mengubah implementasi, dan mengandalkan uji coba JUnit untuk memberi tahu saya bahwa semua fungsionalitas saya masih puas.
  3. Sebagai dokumentasi dari semua kasus penggunaan yang ditangani kode saya, dan bertindak sebagai spek untuk refactoring - seandainya kode perlu ditulis ulang. (Refactor kodenya, dan jika tes jUnit saya gagal - Anda mungkin melewatkan beberapa use case).

Saya tidak mengerti mengapa atau kapan Mockito.verify()harus digunakan. Ketika saya melihat verify()dipanggil, ia memberi tahu saya bahwa jUnit saya menjadi sadar akan implementasinya. (Dengan demikian mengubah implementasi saya akan merusak jUnits saya, meskipun fungsi saya tidak terpengaruh).

Saya mencari:

  1. Apa yang seharusnya menjadi pedoman untuk penggunaan yang tepat Mockito.verify()?

  2. Apakah secara fundamental benar bagi jUnits untuk menyadari, atau sangat erat dengan, implementasi kelas yang diuji?


1
Saya mencoba untuk tidak menggunakan verifikasi () sebanyak yang saya bisa, untuk alasan yang sama seperti yang Anda lihat (saya tidak ingin unit test saya menyadari implementasi), tetapi ada kasus ketika saya tidak punya pilihan - metode void yang di-stub. Secara umum karena mereka tidak mengembalikan apa pun, mereka tidak berkontribusi pada output 'aktual' Anda; tapi tetap saja, Anda perlu tahu bahwa itu disebut. Tetapi saya setuju dengan Anda, tidak masuk akal untuk menggunakan verifikasi untuk memverifikasi alur eksekusi.
Legna

Jawaban:


78

Jika kontrak kelas A menyertakan fakta bahwa ia memanggil metode B dari objek tipe C, maka Anda harus mengujinya dengan membuat tiruan tipe C, dan memverifikasi bahwa metode B telah dipanggil.

Ini menyiratkan bahwa kontrak kelas A memiliki detail yang cukup yang membahas tentang tipe C (yang mungkin merupakan antarmuka atau kelas). Jadi ya, kita berbicara tentang tingkat spesifikasi yang melampaui sekadar "persyaratan sistem", dan menjelaskan cara implementasi.

Ini normal untuk pengujian unit. Ketika Anda menguji unit, Anda ingin memastikan bahwa setiap unit melakukan "hal yang benar", dan itu biasanya akan mencakup interaksinya dengan unit lain. "Unit" di sini mungkin berarti kelas, atau himpunan bagian yang lebih besar dari aplikasi Anda.

Memperbarui:

Saya merasa bahwa ini tidak hanya berlaku untuk verifikasi, tetapi juga untuk stubbing. Segera setelah Anda mematikan metode kelas kolaborator, pengujian unit Anda, dalam beberapa hal, menjadi tergantung pada implementasi. Semacam sifat unit test begitu. Karena Mockito lebih banyak tentang stubbing seperti halnya tentang verifikasi, fakta bahwa Anda menggunakan Mockito sama sekali menyiratkan bahwa Anda akan mengalami ketergantungan seperti ini.

Dalam pengalaman saya, jika saya mengubah implementasi suatu kelas, saya sering harus mengubah implementasi unit test-nya agar sesuai. Namun, biasanya, saya tidak perlu mengubah inventaris unit test apa yang tersedia untuk kelas; kecuali tentu saja, alasan perubahan itu adalah adanya suatu kondisi yang gagal saya uji sebelumnya.

Jadi ini adalah tentang tes unit. Tes yang tidak mengalami ketergantungan seperti ini pada cara kelas kolaborator digunakan sebenarnya adalah tes sub-sistem atau tes integrasi. Tentu saja, ini sering ditulis dengan JUnit juga, dan sering melibatkan penggunaan ejekan. Menurut pendapat saya, "JUnit" adalah nama yang mengerikan, untuk produk yang memungkinkan kami menghasilkan semua jenis pengujian.


8
Terima kasih, David. Setelah memindai melalui beberapa set kode, ini tampak seperti praktik yang umum - tetapi bagi saya, ini mengalahkan tujuan membuat unit test, dan hanya menambahkan biaya tambahan untuk mempertahankannya dengan nilai yang sangat kecil. Saya mengerti mengapa tiruan diperlukan, dan mengapa dependensi untuk mengeksekusi tes perlu diatur. Tetapi memverifikasi bahwa metode dependencyA.XYZ () dijalankan membuat tes sangat rapuh, menurut saya.
Russell

@Russell Bahkan jika "tipe C" adalah antarmuka untuk pembungkus di sekitar perpustakaan, atau sekitar beberapa subsistem aplikasi Anda yang berbeda?
Dawood ibn Kareem

1
Saya tidak akan mengatakan itu sama sekali tidak berguna untuk memastikan beberapa sub sistem atau layanan dipanggil-hanya saja harus ada beberapa pedoman di sekitarnya (merumuskan mereka adalah apa yang ingin saya lakukan). Sebagai contoh: (Saya mungkin terlalu menyederhanakan) Katakanlah, saya menggunakan StrUtil.equals () dalam kode saya, dan memutuskan untuk beralih ke StrUtil.equalsIgnoreCase () dalam implementasi. Jika jUnit telah memverifikasi (StrUtil.equals ), pengujian saya bisa gagal meskipun penerapannya akurat. Panggilan verifikasi ini, IMO, adalah praktik yang buruk meskipun untuk perpustakaan / sub-sistem. Di sisi lain, menggunakan verifikasi untuk memastikan panggilan ke closeDbConn mungkin merupakan usecase yang valid.
Russell

1
Saya memahami Anda dan sepenuhnya setuju dengan Anda. Tetapi saya juga merasa bahwa menulis pedoman yang Anda uraikan dapat diperluas menjadi penulisan seluruh buku teks TDD atau BDD. Untuk mengambil contoh Anda, memanggil equals()atau equalsIgnoreCase()tidak akan pernah menjadi sesuatu yang ditentukan dalam persyaratan kelas, jadi tidak akan pernah memiliki unit test per se. Namun, "menutup koneksi DB ketika selesai" (apa pun artinya dalam hal implementasi) mungkin merupakan persyaratan kelas, meskipun itu bukan "persyaratan bisnis". Bagi saya, ini berkaitan dengan hubungan antara kontrak ...
Dawood ibn Kareem

... dari kelas sebagaimana dinyatakan dalam persyaratan bisnisnya, dan serangkaian metode uji yang menguji unit kelas itu. Mendefinisikan hubungan ini akan menjadi topik penting dalam buku apa pun tentang TDD atau BDD. Sementara seseorang dalam tim Mockito dapat menulis posting tentang topik ini untuk wiki mereka, saya tidak melihat perbedaannya dengan banyak literatur lain yang tersedia. Jika Anda melihat perbedaannya, beri tahu saya, dan mungkin kita bisa mengerjakannya bersama.
Dawood ibn Kareem

60

Jawaban David tentu saja benar tetapi tidak menjelaskan mengapa Anda menginginkan ini.

Pada dasarnya, ketika pengujian unit Anda menguji unit fungsionalitas secara terpisah. Anda menguji apakah input menghasilkan output yang diharapkan. Terkadang, Anda harus menguji efek samping juga. Singkatnya, verifikasi memungkinkan Anda untuk melakukan itu.

Misalnya Anda memiliki sedikit logika bisnis yang seharusnya menyimpan sesuatu menggunakan DAO. Anda bisa melakukan ini dengan menggunakan tes integrasi yang instantiate DAO, menghubungkannya dengan logika bisnis dan kemudian mencari-cari di dalam database untuk melihat apakah barang-barang yang diharapkan disimpan. Itu bukan tes unit lagi.

Atau, Anda dapat mengejek DAO dan memverifikasi bahwa itu dipanggil dengan cara yang Anda harapkan. Dengan mockito Anda dapat memverifikasi bahwa sesuatu dipanggil, seberapa sering itu dipanggil, dan bahkan menggunakan pencocokan pada parameter untuk memastikan itu dipanggil dengan cara tertentu.

Sisi lain dari pengujian unit seperti ini adalah memang Anda mengikat tes untuk implementasi yang membuat refactoring sedikit lebih sulit. Di sisi lain, bau desain yang baik adalah jumlah kode yang diperlukan untuk menggunakannya dengan benar. Jika tes Anda harus sangat lama, mungkin ada sesuatu yang salah dengan desainnya. Jadi kode dengan banyak efek samping / interaksi kompleks yang perlu diuji mungkin bukan hal yang baik untuk dimiliki.


30

Ini pertanyaan yang bagus! Saya pikir akar penyebabnya adalah sebagai berikut, kami menggunakan JUnit tidak hanya untuk pengujian unit. Jadi pertanyaannya harus dipecah:

  • Haruskah saya menggunakan Mockito.verify () dalam pengujian integrasi (atau pengujian lebih tinggi dari unit lainnya)?
  • Haruskah saya menggunakan Mockito.verify () dalam pengujian unit kotak hitam saya ?
  • Haruskah saya menggunakan Mockito.verify () dalam pengujian unit kotak putih saya ?

jadi jika kita akan mengabaikan pengujian yang lebih tinggi dari unit, pertanyaannya dapat diulang kembali " Menggunakan pengujian unit kotak putih dengan Mockito.verify () menciptakan pasangan yang hebat antara pengujian unit dan implementasi saya, dapatkah saya membuat " kotak abu-abu " pengujian unit dan aturan praktis apa yang harus saya gunakan untuk ini ".

Sekarang, mari kita lalui semua langkah demi langkah ini.

* - Haruskah saya menggunakan Mockito.verify () dalam pengujian integrasi (atau pengujian lain yang lebih tinggi dari unit)? * Saya kira jawabannya jelas tidak, apalagi Anda tidak boleh menggunakan ejekan untuk ini. Tes Anda harus sedekat mungkin dengan aplikasi nyata. Anda menguji kasus penggunaan lengkap, bukan bagian yang terisolasi dari aplikasi.

* black-box vs white-box unit-testing * Jika Anda menggunakan pendekatan black-box apa yang sebenarnya Anda lakukan, Anda memberikan input (semua kelas ekuivalensi) input, keadaan , dan tes yang akan menerima output yang diharapkan. Dalam pendekatan ini penggunaan mock pada umumnya dibenarkan (Anda hanya meniru bahwa mereka melakukan hal yang benar; Anda tidak ingin mengujinya), tetapi memanggil Mockito.verify () tidak perlu.

Jika Anda menggunakan pendekatan kotak putih apa yang sebenarnya Anda lakukan, Anda menguji perilaku unit Anda. Dalam pendekatan ini, menelepon ke Mockito.verify () sangat penting, Anda harus memverifikasi bahwa unit Anda berperilaku seperti yang Anda harapkan.

aturan praktis untuk pengujian kotak abu-abu Masalah dengan pengujian kotak putih adalah menciptakan kopling tinggi. Salah satu solusi yang mungkin adalah melakukan pengujian kotak abu-abu, bukan pengujian kotak putih. Ini adalah semacam kombinasi pengujian kotak hitam & putih. Anda benar-benar menguji perilaku unit Anda seperti dalam pengujian white-box, tetapi secara umum Anda menjadikannya agnostik implementasi bila memungkinkan . Jika memungkinkan, Anda hanya akan membuat centang seperti dalam kotak hitam, hanya menegaskan bahwa output adalah apa yang Anda harapkan. Jadi, inti dari pertanyaan Anda adalah kapan mungkin.

Ini sangat sulit. Saya tidak memiliki contoh yang baik, tetapi saya bisa memberi Anda contoh. Dalam kasus yang disebutkan di atas dengan equals () vs equalsIgnoreCase () Anda tidak boleh memanggil Mockito.verify (), hanya menyatakan output. Jika Anda tidak bisa melakukannya, pecah kode Anda ke unit yang lebih kecil, sampai Anda bisa melakukannya. Di sisi lain, anggaplah Anda memiliki beberapa @Service dan Anda sedang menulis @ Web-Service yang pada dasarnya membungkus @Service Anda - ia mendelegasikan semua panggilan ke @Service (dan membuat beberapa penanganan kesalahan tambahan). Dalam hal ini pemanggilan ke Mockito.verify () penting, Anda tidak boleh menduplikasi semua cek yang Anda lakukan untuk @Serive, memverifikasi bahwa Anda menelepon ke @Service dengan daftar parammeter yang benar sudah cukup.


Pengujian kotak abu-abu agak sulit. Saya cenderung untuk membatasi hal-hal seperti DAO. Saya telah mengerjakan beberapa proyek dengan pembangunan yang sangat lambat karena banyaknya tes kotak abu-abu, hampir tidak ada unit test dan terlalu banyak tes blackbox untuk mengimbangi kurangnya kepercayaan pada apa yang seharusnya diuji oleh tes greybox.
Jilles van Gurp

Bagi saya ini adalah jawaban terbaik yang tersedia karena ini menjawab kapan harus menggunakan Mockito.when () dalam berbagai situasi. Sudah selesai dilakukan dengan baik.
Michiel Leegwater

8

Saya harus mengatakan, bahwa Anda benar dari sudut pandang pendekatan klasik:

  • Jika Anda pertama kali membuat (atau mengubah) logika bisnis aplikasi Anda dan kemudian menutupinya dengan (mengadopsi) tes ( pendekatan Test-Last ), maka akan sangat menyakitkan dan berbahaya untuk membiarkan tes tahu apa pun tentang cara kerja perangkat lunak Anda, selain memeriksa input dan output.
  • Jika Anda mempraktikkan pendekatan Test-Driven , maka tes Anda adalah yang pertama kali ditulis, diubah, dan mencerminkan kasus penggunaan fungsi perangkat lunak Anda. Implementasinya tergantung pada tes. Itu kadang-kadang berarti, bahwa Anda ingin perangkat lunak Anda diimplementasikan dalam beberapa cara tertentu, misalnya mengandalkan metode beberapa komponen lain atau bahkan menyebutnya jumlah waktu tertentu. Di situlah Mockito.verify () berguna!

Penting untuk diingat, bahwa tidak ada alat universal. Jenis perangkat lunak, ukurannya, tujuan perusahaan dan situasi pasar, keterampilan tim, dan banyak hal lainnya memengaruhi keputusan pendekatan mana yang akan digunakan pada kasus khusus Anda.


0

Seperti yang dikatakan beberapa orang

  1. Terkadang Anda tidak memiliki output langsung yang dapat Anda tegaskan
  2. Terkadang Anda hanya perlu mengonfirmasi bahwa metode yang Anda uji mengirimkan output tidak langsung yang benar ke kolaboratornya (yang Anda ejek).

Mengenai kekhawatiran Anda tentang melanggar tes Anda saat refactoring, itu agak diharapkan saat menggunakan tiruan / bertopik / mata-mata. Maksud saya secara definisi dan bukan mengenai implementasi spesifik seperti Mockito. Tapi Anda bisa berpikir dengan cara ini - jika Anda perlu melakukan refactoring yang akan membuat perubahan besar pada cara kerja metode Anda, itu ide yang baik untuk melakukannya pada pendekatan TDD, artinya Anda dapat mengubah tes Anda terlebih dahulu untuk menentukan perilaku baru (yang akan gagal tes), dan kemudian lakukan perubahan dan dapatkan tes lulus lagi.


0

Dalam kebanyakan kasus ketika orang tidak suka menggunakan Mockito.verify, itu karena ia digunakan untuk memverifikasi semua yang dilakukan unit yang diuji dan itu berarti Anda harus menyesuaikan tes Anda jika ada perubahan di dalamnya. Tapi, saya pikir itu bukan masalah. Jika Anda ingin dapat mengubah apa yang dilakukan suatu metode tanpa perlu mengubah tesnya, itu pada dasarnya berarti Anda ingin menulis tes yang tidak menguji semua yang dilakukan metode Anda, karena Anda tidak ingin ia menguji perubahan Anda. . Dan itu adalah cara berpikir yang salah.

Apa yang sebenarnya merupakan masalah, adalah jika Anda dapat memodifikasi apa yang dilakukan metode Anda dan uji unit yang seharusnya mencakup fungsionalitas sepenuhnya tidak gagal. Itu berarti bahwa apa pun maksud perubahan Anda, hasil perubahan Anda tidak tercakup oleh ujian.

Karena itu, saya lebih suka mengejek sebanyak mungkin: juga mengejek objek data Anda. Ketika melakukan itu, Anda tidak hanya dapat menggunakan verifikasi untuk memeriksa bahwa metode yang benar dari kelas lain dipanggil, tetapi juga bahwa data yang dikirimkan dikumpulkan melalui metode yang benar dari objek data tersebut. Dan untuk membuatnya lengkap, Anda harus menguji urutan di mana panggilan terjadi. Contoh: jika Anda memodifikasi objek entitas db dan kemudian menyimpannya menggunakan repositori, itu tidak cukup untuk memverifikasi bahwa setter objek dipanggil dengan data yang benar dan bahwa metode penyimpanan repositori dipanggil. Jika mereka dipanggil dengan urutan yang salah, metode Anda masih tidak melakukan apa yang seharusnya dilakukan. Jadi, saya tidak menggunakan Mockito.verify tapi saya membuat objek inOrder dengan semua ejekan dan menggunakan inOrder.verify sebagai gantinya. Dan jika Anda ingin membuatnya lengkap, Anda juga harus menghubungi Mockito. verifikasi NoMoreInteractions di akhir dan lulus semua mengejek. Kalau tidak, seseorang dapat menambahkan fungsionalitas / perilaku baru tanpa mengujinya, yang berarti setelah sementara statistik cakupan Anda dapat 100% dan Anda masih menumpuk kode yang tidak dinyatakan atau diverifikasi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.