Haruskah saya menguji subclass atau kelas induk abstrak saya?

12

Saya memiliki implementasi kerangka, seperti pada Butir 18 dari Java Efektif (diskusi diperpanjang di sini ). Ini adalah kelas abstrak yang menyediakan 2 metode metode publik, methodA () dan methodB () yang memanggil metode subclass untuk "mengisi kesenjangan" yang tidak bisa saya definisikan secara abstrak.

Saya mengembangkannya terlebih dahulu dengan membuat kelas konkret dan menulis unit test untuknya. Ketika kelas kedua datang, saya dapat mengekstraksi perilaku umum, mengimplementasikan "celah yang hilang" di kelas kedua dan siap (tentu saja, unit test dibuat untuk subclass kedua).

Waktu berlalu dan sekarang saya memiliki 4 subclass, masing-masing menerapkan 3 metode terproteksi pendek yang sangat spesifik untuk implementasi konkretnya, sementara implementasi kerangka melakukan semua pekerjaan umum.

Masalah saya adalah ketika saya membuat implementasi baru, saya menulis semua tes lagi:

  • Apakah subclass memanggil metode yang diperlukan?
  • Apakah metode yang diberikan memiliki anotasi yang diberikan?
  • Apakah saya mendapatkan hasil yang diharapkan dari metode A?
  • Apakah saya mendapatkan hasil yang diharapkan dari metode B?

Sambil melihat manfaat dengan pendekatan ini:

  • Ini mendokumentasikan persyaratan subkelas melalui tes
  • Ini mungkin gagal cepat jika terjadi refactoring yang bermasalah
  • Uji subclass secara keseluruhan dan melalui API publik mereka ("apakah methodA () berfungsi?" Dan tidak "apakah metode yang dilindungi ini berfungsi?")

Masalah yang saya miliki adalah bahwa tes untuk subclass baru pada dasarnya adalah no-brainer: - Tes semua sama di semua subclass, mereka hanya mengubah jenis pengembalian metode (implementasi kerangka adalah generik) dan properti I periksa bagian tegas dari tes. - Biasanya subclass tidak memiliki tes yang khusus untuk mereka

Saya suka cara tes fokus pada hasil subclass itu sendiri dan melindunginya dari refactoring, tetapi kenyataan bahwa pelaksanaan tes menjadi hanya "pekerjaan manual" membuat saya berpikir saya melakukan sesuatu yang salah.

Apakah masalah ini umum ketika menguji hierarki kelas? Bagaimana saya bisa menghindarinya?

ps: Saya berpikir untuk menguji kelas kerangka, tetapi juga aneh membuat tiruan dari kelas abstrak untuk dapat mengujinya. Dan saya berpikir bahwa setiap refactoring pada kelas abstrak yang mengubah perilaku yang tidak diharapkan oleh subclass tidak akan diperhatikan secepat ini. Nyali saya memberi tahu saya bahwa menguji subclass "secara keseluruhan" adalah pendekatan yang lebih disukai di sini, tetapi jangan ragu untuk memberi tahu saya bahwa saya salah :)

ps2: Saya sudah mencari di Google dan menemukan banyak pertanyaan tentang subjek ini. Salah satunya adalah ini yang memiliki jawaban bagus dari Nigel Thorne, kasus saya akan menjadi "nomor 1." Itu bagus, tapi saya tidak bisa menolak pada saat ini, jadi saya harus hidup dengan masalah ini. Jika saya bisa refactor, saya akan memiliki masing-masing subclass sebagai strategi. Itu akan baik-baik saja, tetapi saya masih akan menguji "kelas utama" dan menguji strategi, tetapi saya tidak akan melihat adanya refactoring yang merusak integrasi antara kelas utama dan strategi.

ps3: Saya telah menemukan beberapa jawaban yang mengatakan bahwa "dapat diterima" untuk menguji kelas abstrak. Saya setuju bahwa ini dapat diterima, tetapi saya ingin tahu pendekatan mana yang lebih disukai dalam kasus ini (saya masih mulai dengan unit test)

JSBach
sumber
2
Tulis kelas tes abstrak dan mintalah tes konkret Anda mewarisinya, mencerminkan struktur kode bisnis. Tes seharusnya menguji perilaku dan bukan implementasi unit, tetapi itu tidak berarti Anda tidak dapat menggunakan pengetahuan orang dalam tentang sistem untuk mencapai tujuan itu dengan lebih efisien.
Kilian Foth
Saya membaca di suatu tempat bahwa seseorang tidak boleh menggunakan warisan pada tes, saya mencari sumber untuk membacanya dengan cermat
JSBach
4
@KilianFoth, apakah Anda yakin dengan saran ini? Ini terdengar seperti resep untuk pengujian unit yang rapuh, sangat sensitif terhadap perubahan desain dan karenanya tidak dapat dipertahankan.
Konrad Morawski

Jawaban:

5

Saya tidak bisa melihat bagaimana Anda akan menulis tes untuk kelas dasar abstrak ; Anda tidak dapat membuat instance, jadi Anda tidak dapat memiliki instance untuk menguji. Anda bisa membuat "dummy", subkelas beton hanya untuk keperluan mengujinya - tidak yakin berapa banyak gunanya. YMMV.

Setiap kali Anda membuat implementasi baru (kelas), maka Anda harus menulis tes yang melatih implementasi baru itu. Karena bagian dari fungsionalitas ("celah" Anda) diimplementasikan dalam setiap subkelas, ini mutlak diperlukan; output dari masing-masing metode yang diwariskan mungkin (dan mungkin harus ) berbeda untuk setiap implementasi baru.

Phill W.
sumber
Ya, mereka berbeda (jenis dan isinya berbeda). Secara teknis, saya akan dapat mengejek kelas ini atau memiliki implementasi sederhana hanya untuk tujuan pengujian dengan implementasi kosong. Saya tidak suka pendekatan ini. Tetapi kenyataan bahwa saya tidak "perlu" untuk berpikir untuk lulus tes dalam implementasi baru membuat saya merasa aneh dan juga membuat saya mempertanyakan seberapa percaya diri saya dalam tes ini (tentu saja, jika saya mengubah kelas dasar dengan sengaja, tes gagal, yang merupakan bukti bahwa saya dapat mempercayai mereka)
JSBach
4

Biasanya Anda akan membuat kelas dasar untuk menegakkan antarmuka. Anda ingin menguji antarmuka itu, bukan detailnya di bawah. Oleh karena itu, Anda harus membuat tes yang membuat setiap kelas secara individual tetapi hanya menguji melalui metode kelas dasar.

Kelebihan dari metode ini adalah karena Anda menguji antarmuka, kini Anda dapat melakukan refactor di bawah antarmuka. Anda mungkin menemukan bahwa kode dalam kelas anak harus di orangtua, atau sebaliknya. Sekarang tes Anda akan membuktikan bahwa Anda tidak menghentikan perilaku ketika Anda memindahkan kode itu.

Secara umum, Anda harus menguji kode bagaimana kode itu dimaksudkan untuk digunakan. Itu berarti menghindari pengujian metode pribadi, dan seringkali berarti bahwa unit Anda yang diuji mungkin sedikit lebih besar dari yang Anda bayangkan - mungkin sekelompok kelas daripada satu kelas. Sasaran Anda adalah mengisolasi perubahan sehingga Anda jarang harus mengubah tes saat refactor dalam lingkup yang diberikan.

Michael K.
sumber