Apakah memata-matai praktik buruk di kelas teruji?

13

Saya sedang mengerjakan sebuah proyek di mana panggilan internal kelas biasa tetapi hasilnya berkali-kali nilai sederhana. Contoh ( bukan kode sebenarnya ):

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

Tes unit menulis untuk jenis kode ini benar-benar sulit karena saya hanya ingin menguji sistem kondisi dan bukan penerapan kondisi aktual. (Saya melakukan itu dalam tes terpisah.) Sebenarnya akan lebih baik jika saya melewati fungsi yang menerapkan kondisi dan dalam tes saya cukup memberikan beberapa tiruan. Masalah dengan pendekatan ini adalah kebisingan: kami banyak menggunakan obat generik .

Solusi yang berfungsi; Namun, adalah untuk membuat objek yang diuji menjadi mata - mata dan mengejek panggilan ke fungsi internal.

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

Kekhawatiran di sini adalah bahwa implementasi SUT diubah secara efektif dan mungkin bermasalah untuk menjaga agar tes tetap sinkron dengan implementasi. Apakah ini benar? Apakah ada praktik terbaik untuk menghindari gangguan pemanggilan metode internal ini?

Perhatikan bahwa kita berbicara tentang bagian-bagian suatu algoritma, jadi membaginya ke beberapa kelas mungkin bukan keputusan yang diinginkan.

allprog
sumber

Jawaban:

15

Tes unit harus memperlakukan kelas yang mereka uji sebagai kotak hitam. Satu-satunya hal yang penting adalah bahwa metode publiknya berperilaku seperti yang diharapkan. Bagaimana kelas mencapai ini melalui metode internal dan privat negara tidak masalah.

Ketika Anda merasa bahwa tidak mungkin membuat tes yang bermakna seperti ini, itu adalah pertanda bahwa kelas Anda terlalu kuat dan melakukan terlalu banyak. Anda harus mempertimbangkan untuk memindahkan beberapa fungsinya ke kelas yang terpisah yang dapat diuji secara terpisah.

Philipp
sumber
1
Saya memahami ide pengujian unit sejak lama dan telah menulis banyak dari mereka dengan sukses. Ini hanya menipu bahwa sesuatu terlihat sederhana di atas kertas, terlihat lebih buruk dalam kode, dan akhirnya saya dihadapkan dengan sesuatu yang memiliki antarmuka yang sangat sederhana tetapi mengharuskan saya untuk mengejek setengah dari dunia di sekitar input.
allprog
@allprog Ketika Anda perlu melakukan banyak ejekan, sepertinya Anda memiliki terlalu banyak ketergantungan di antara kelas Anda. Apakah Anda mencoba mengurangi sambungan di antara mereka?
Philipp
@allprog jika Anda berada dalam situasi itu, desain kelas yang harus disalahkan.
itsbruce
Itu adalah model data yang menyebabkan sakit kepala. Itu harus mematuhi aturan ORM dan banyak persyaratan lainnya. Dengan logika bisnis murni dan kode kewarganegaraan, jauh lebih mudah untuk melakukan pengujian unit dengan benar.
allprog
3
Tes unit tidak perlu menangani SUT sebagai backbox. Inilah sebabnya mereka disebut unit test. Dengan mengejek dependensi saya dapat mempengaruhi lingkungan dan untuk mengetahui apa yang harus saya tiru, saya juga harus mengetahui beberapa internal. Tetapi tentu saja ini tidak berarti bahwa SUT harus diubah dengan cara apa pun. Namun, mata-mata memungkinkan untuk melakukan beberapa perubahan.
allprog
4

Jika keduanya findError()dan checkFirstCondition()lain-lain adalah metode publik untuk kelas Anda, maka findError()secara efektif fasad untuk fungsi yang sudah tersedia dari API yang sama. Tidak ada yang salah dengan itu, tetapi itu berarti bahwa Anda harus menulis tes untuk itu yang sangat mirip dengan tes yang sudah ada. Duplikasi ini hanya mencerminkan duplikasi di antarmuka publik Anda. Itu bukan alasan untuk memperlakukan metode ini berbeda dari yang lain.

Kilian Foth
sumber
Metode internal dibuat publik hanya karena mereka perlu diuji dan saya tidak ingin subklas SUT atau memasukkan tes unit dalam kelas SUT sebagai kelas dalam statis. Tapi saya mengerti maksud Anda. Namun, saya tidak dapat menemukan garis panduan yang baik untuk menghindari situasi semacam ini. Tutorial selalu macet di tingkat dasar yang tidak ada hubungannya dengan perangkat lunak nyata. Kalau tidak, alasan untuk memata-matai adalah persis untuk menghindari duplikasi kode uji dan membuat unit tes lingkup.
allprog
3
Saya tidak setuju bahwa metode helper harus dipublikasikan untuk pengujian unit yang tepat. Jika kontrak suatu metode menyatakan bahwa ia memeriksa berbagai kondisi, maka tidak ada yang salah dengan menulis beberapa tes terhadap metode publik yang sama, satu untuk setiap "subkontrak". Maksud unit test adalah untuk mencapai cakupan semua kode, bukan untuk mencapai cakupan yang dangkal dari metode publik melalui korespondensi metode-uji 1: 1.
Kilian Foth
Hanya menggunakan API publik untuk pengujian berkali-kali secara signifikan lebih kompleks daripada menguji potongan internal satu per satu. Saya tidak membantah, saya mendapatkan bahwa pendekatan ini bukan yang terbaik dan memiliki pandangan belakang yang ditunjukkan oleh pertanyaan saya. Masalah terbesar adalah bahwa fungsi tidak dapat dikomposisikan di Jawa dan solusinya sangat singkat. Tetapi sepertinya tidak ada solusi lain untuk pengujian unit nyata.
allprog
4

Tes unit harus menguji kontrak; itu satu-satunya hal yang penting bagi mereka. Menguji apa pun yang bukan bagian dari kontrak tidak hanya membuang-buang waktu, itu merupakan sumber kesalahan potensial. Setiap kali Anda melihat pengembang mengubah tes ketika dia mengubah detail implementasi, bel alarm akan berbunyi; pengembang itu mungkin (baik sengaja atau tidak) menyembunyikan kesalahannya. Sengaja menguji detail implementasi memaksa kebiasaan buruk ini, membuatnya lebih mungkin bahwa kesalahan akan ditutup-tutupi.

Panggilan internal adalah detail implementasi dan seharusnya hanya menarik dalam mengukur kinerja . Yang biasanya bukan tugas unit test.

itu otaknya
sumber
Kedengarannya bagus. Namun pada kenyataannya, "string" yang harus saya ketik dan sebut kode itu adalah dalam bahasa yang hanya tahu sedikit tentang fungsi. Secara teori saya dapat dengan mudah menggambarkan masalah dan membuat pergantian di sana-sini untuk menyederhanakannya. Dalam kode saya harus menambahkan banyak noise sintaksis untuk mencapai fleksibilitas ini yang membuat saya tidak bisa menggunakannya. Jika metode aberisi panggilan ke metode bdi kelas yang sama, maka tes aharus menyertakan tes b. Dan tidak ada cara untuk mengubah ini selama btidak dilewatkan asebagai parameter Tapi tidak ada solusi lain, saya mengerti.
allprog
1
Jika bmerupakan bagian dari antarmuka publik, itu harus tetap diuji. Jika tidak, tidak perlu diuji. Jika Anda mempublikasikannya hanya karena Anda ingin mengujinya, Anda melakukan kesalahan.
itsbruce
Lihat komentar saya untuk jawaban @ Philip. Saya belum menyebutkan tetapi model data adalah sumber kejahatan. Kode murni dan tanpa kewarganegaraan adalah sepotong kue.
allprog
2

Pertama, saya bertanya-tanya apa yang sulit untuk diuji tentang fungsi contoh yang Anda tulis? Sejauh yang saya bisa lihat, Anda cukup mengirimkan berbagai input dan memeriksa untuk memastikan nilai boolean yang benar dikembalikan. Apa yang saya lewatkan?

Sedangkan untuk mata-mata, jenis yang disebut pengujian "kotak putih" yang menggunakan mata-mata dan ejekan adalah perintah yang besarnya lebih banyak pekerjaan untuk ditulis, bukan hanya karena ada begitu banyak kode uji untuk ditulis, tetapi setiap saat implementasinya berubah, Anda juga harus mengubah tes (bahkan jika antarmuka tetap sama). Dan pengujian semacam ini juga kurang dapat diandalkan dibandingkan pengujian kotak hitam, karena Anda perlu memastikan bahwa semua kode pengujian tambahan itu benar, dan meskipun Anda dapat percaya bahwa pengujian unit kotak hitam akan gagal jika tidak cocok dengan antarmuka. , Anda tidak dapat mempercayai soal kode yang terlalu sering mengejek karena terkadang tes bahkan tidak menguji banyak kode nyata - hanya tiruan. Jika tiruannya salah, kemungkinan tes Anda akan berhasil, tetapi kode Anda masih rusak.

Siapa pun yang memiliki pengalaman dengan pengujian white-box dapat memberi tahu Anda bahwa mereka payah untuk menulis dan memelihara. Ditambah dengan fakta bahwa mereka kurang dapat diandalkan, pengujian kotak putih jauh lebih rendah dalam banyak kasus.

BT
sumber
Terima kasih atas catatannya. Contoh fungsi adalah urutan besarnya lebih sederhana daripada apa pun yang Anda harus tulis dalam algoritma yang kompleks. Sebenarnya, pertanyaannya ternyata lebih seperti: apakah bermasalah untuk menguji algoritma dengan mata-mata di beberapa bagian. Ini bukan kode stateful, semua status dipisahkan menjadi argumen input. Masalahnya adalah fakta bahwa saya ingin menguji fungsi kompleks dalam contoh tanpa harus memberikan parameter yang waras untuk sub fungsi.
allprog
Dengan dimulainya pemrograman fungsional di Java 8 ini telah menjadi sedikit lebih elegan tetapi tetap menjaga fungsionalitas dalam satu kelas dapat menjadi pilihan yang lebih baik dalam hal algoritma daripada mengekstraksi bagian yang berbeda (sendirian tidak berguna) menjadi "digunakan sekali" kelas hanya karena testability. Dalam hal ini mata-mata melakukan hal yang sama seperti mengolok-olok tetapi tanpa harus meledakkan kode yang koheren secara visual. Sebenarnya, kode pengaturan yang sama digunakan seperti dengan mengejek. Saya suka menghindari ekstrem, setiap jenis tes mungkin sesuai di tempat-tempat tertentu. Pengujian entah bagaimana jauh lebih baik daripada tidak. :)
allprog
"Saya ingin menguji fungsi yang kompleks .. tanpa harus memberikan parameter yang waras untuk sub fungsi" - Saya tidak mengerti maksud Anda di sana. Sub fungsi yang mana? Apakah Anda berbicara tentang fungsi internal yang digunakan oleh 'fungsi kompleks'?
BT
Itulah gunanya memata-matai dalam kasus saya. Fungsi internal cukup rumit untuk dikendalikan. Bukan karena kode tetapi karena mereka mengimplementasikan sesuatu yang secara logis kompleks. Memindahkan barang di kelas yang berbeda adalah pilihan alami tetapi fungsi itu saja tidak berguna sama sekali. Oleh karena itu, menjaga kelas bersama-sama dan mengendalikannya dengan fungsi mata-mata ternyata menjadi pilihan yang lebih baik. Telah bekerja dengan sempurna selama hampir satu tahun, dan dapat dengan mudah menahan perubahan model. Saya tidak menggunakan pola ini sejak saat itu, hanya berpikir bagus untuk menyebutkan bahwa itu layak dalam beberapa kasus.
allprog
@allprog "logis logis" - Jika kompleks, Anda perlu tes kompleks. Tidak ada jalan lain untuk itu. Mata-mata hanya akan membuatnya lebih sulit dan lebih kompleks untuk Anda. Anda harus membuat sub-fungsi yang dapat dimengerti yang dapat Anda uji sendiri, daripada menggunakan mata-mata untuk menguji perilaku khusus mereka di dalam fungsi lain.
BT