Apakah ini penggunaan metode reset Mockito yang tepat?

68

Saya memiliki metode pribadi di kelas pengujian saya yang membangun objek yang biasa digunakan Bar. The Barkonstruktor panggilan someMethod()metode dalam objek saya mengejek:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

Dalam beberapa metode pengujian saya, saya ingin memeriksa someMethodjuga dipanggil oleh tes tertentu. Sesuatu seperti yang berikut ini:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

Ini gagal, karena objek yang diejek telah someMethoddipanggil dua kali. Saya tidak ingin metode pengujian saya peduli dengan efek samping getBar()metode saya , jadi apakah masuk akal untuk mengatur ulang objek tiruan saya di akhir getBar()?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

Saya bertanya, karena dokumentasi menyarankan menyetel ulang objek tiruan umumnya merupakan indikasi tes buruk. Namun, ini terasa baik bagi saya.

Alternatif

Pilihan alternatif tampaknya memanggil:

verify(mockedObject, times(2)).someMethod();

yang menurut saya memaksa setiap tes untuk mengetahui tentang harapan getBar(), tanpa hasil.

Duncan Jones
sumber

Jawaban:

60

Saya percaya ini adalah salah satu kasus di mana penggunaannya reset()ok. Tes yang Anda tulis adalah pengujian bahwa "beberapa hal" memicu satu panggilan someMethod(). Menulis verify()pernyataan dengan jumlah doa yang berbeda dapat menyebabkan kebingungan.

  • atLeastOnce() memungkinkan positif palsu, yang merupakan hal buruk karena Anda ingin tes Anda selalu benar.
  • times(2)mencegah false positive, tetapi membuatnya tampak seperti Anda mengharapkan dua doa daripada mengatakan "saya tahu konstruktor menambahkan satu". Lebih jauh lagi, jika ada perubahan pada konstruktor untuk menambahkan panggilan tambahan, tes sekarang memiliki peluang untuk false positive. Dan menghapus panggilan akan menyebabkan tes gagal karena tes sekarang salah, bukan apa yang diuji salah.

Dengan menggunakan reset()metode helper, Anda menghindari kedua masalah ini. Namun, Anda harus berhati-hati karena itu juga akan mengatur ulang kesalahan apa pun yang telah Anda lakukan, jadi berhati-hatilah. Alasan utama reset()tidak dianjurkan adalah untuk mencegah

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

Ini bukan yang OP coba lakukan. OP, saya berasumsi, memiliki tes yang memverifikasi permohonan dalam konstruktor. Untuk pengujian ini, pengaturan ulang memungkinkan untuk mengisolasi aksi tunggal ini dan efeknya. Ini salah satu dari beberapa kasus dengan reset()dapat membantu. Opsi lain yang tidak menggunakannya semuanya memiliki kontra. Fakta bahwa OP membuat posting ini menunjukkan bahwa ia sedang memikirkan situasi dan tidak hanya secara buta menggunakan metode reset.

unholysampler
sumber
17
Saya berharap Mockito menyediakan resetInteractions () panggilan untuk hanya melupakan interaksi masa lalu untuk keperluan verifikasi (..., times (...)) dan tetap melakukan stubbing. Ini akan membuat situasi pengujian {setup; bertindak; verifikasi;} yang jauh lebih mudah untuk ditangani. Itu akan menjadi {setup; resetInteractions; bertindak; verifikasi}
Arkadiy
2
Sebenarnya sejak Mockito 2.1 itu memang menyediakan cara untuk menghapus doa tanpa mengatur ulang bertopik:Mockito.clearInvocations(T... mocks)
Colin D Bennett
6

Pengguna Smart Mockito jarang menggunakan fitur reset karena mereka tahu itu bisa menjadi pertanda tes yang buruk. Biasanya, Anda tidak perlu mengatur ulang tiruan Anda, cukup buat tiruan baru untuk setiap metode pengujian.

Alih-alih reset()tolong pertimbangkan menulis metode tes yang sederhana, kecil dan fokus pada tes yang panjang dan spesifik. Bau kode potensial pertama ada reset()di tengah-tengah metode pengujian.

Diekstrak dari dokumen mockito .

Saran saya adalah Anda mencoba menghindari penggunaan reset(). Menurut pendapat saya, jika Anda menelepon dua kali ke beberapa Metode, itu harus diuji (mungkin itu adalah akses basis data, atau proses panjang lainnya yang ingin Anda perhatikan).

Jika Anda benar-benar tidak peduli tentang itu, Anda dapat menggunakan:

verify(mockedObject, atLeastOnce()).someMethod();

Perhatikan bahwa yang terakhir ini dapat menyebabkan hasil yang salah, jika Anda memanggil someMethod dari getBar, dan bukan setelah (ini adalah perilaku yang salah, tetapi tes tidak akan gagal).

greuze
sumber
2
Ya, saya telah melihat kutipan yang tepat (saya ditautkan dari pertanyaan saya). Saat ini saya belum melihat argumen yang layak tentang mengapa contoh saya di atas "buruk". Bisakah Anda menyediakannya?
Duncan Jones
Jika Anda perlu mengatur ulang objek tiruan Anda, sepertinya Anda mencoba menguji terlalu banyak barang dalam pengujian Anda. Anda dapat membaginya dalam dua tes, menguji hal-hal kecil. Bagaimanapun, saya tidak tahu mengapa Anda memverifikasi di dalam metode getBar, sulit untuk melacak apa yang Anda uji. Saya merekomendasikan Anda untuk merancang pemikiran tes Anda di apa yang harus dilakukan kelas Anda (jika Anda harus memanggil beberapa Metode tepat dua kali, setidaknya satu kali, hanya satu kali, tidak pernah, dll), dan melakukan setiap verifikasi di tempat yang sama.
greuze
Saya telah mengedit pertanyaan saya untuk menyoroti bahwa masalah tetap ada bahkan jika saya tidak memanggil verifymetode pribadi saya (yang saya setuju, mungkin tidak termasuk di sana). Saya menyambut komentar Anda tentang apakah jawaban Anda akan berubah.
Duncan Jones
Ada banyak alasan bagus untuk menggunakan reset, saya tidak akan terlalu memperhatikan hal ini pada kutipan mockito itu. Anda dapat menggunakan Spring's JUnit Class Runner saat menjalankan test suite menyebabkan interaksi yang tidak diinginkan, terutama jika Anda melakukan pengujian yang melibatkan panggilan basis data yang mengejek atau panggilan yang melibatkan metode pribadi yang Anda tidak ingin menggunakan refleksi pada.
Sandy Simonton
Saya biasanya menemukan ini hal yang sulit ketika saya ingin menguji beberapa hal, tetapi JUnit sama sekali tidak menawarkan cara yang bagus (!) Untuk parameterisasi tes. Tidak seperti NUnit misalnya dengan anotasi.
Stefan Hendriks
3

Benar-benar tidak. Seperti yang sering terjadi, kesulitan yang Anda alami dalam menulis tes bersih adalah tanda bahaya utama tentang desain kode produksi Anda. Dalam hal ini, solusi terbaik adalah dengan memperbaiki kode Anda sehingga konstruktor Bar tidak memanggil metode apa pun.

Konstruktor harus membangun, bukan mengeksekusi logika. Ambil nilai pengembalian metode dan berikan sebagai parameter konstruktor.

new Bar(mockedObject);

menjadi:

new Bar(mockedObject.someMethod());

Jika ini akan menghasilkan duplikasi logika ini di banyak tempat, pertimbangkan membuat metode pabrik yang dapat diuji secara independen dari objek Bar Anda:

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

Jika refactoring ini terlalu sulit maka menggunakan reset () adalah pekerjaan yang bagus. Tapi mari kita perjelas - ini menunjukkan bahwa kode Anda dirancang dengan buruk.

tonicsoft
sumber