Menulis Kode yang Dapat Diuji vs Menghindari Generalitas Spekulatif

11

Saya membaca beberapa posting blog pagi ini, dan menemukan ini :

Jika satu-satunya kelas yang pernah mengimplementasikan antarmuka Pelanggan adalah CustomerImpl, Anda tidak benar-benar memiliki polimorfisme dan substitusi karena pada praktiknya tidak ada yang menggantikan pada saat runtime. Itu adalah generalitas palsu.

Itu masuk akal bagi saya, karena mengimplementasikan antarmuka menambah kompleksitas dan, jika hanya ada satu implementasi, orang mungkin berpendapat bahwa itu menambah kompleksitas yang tidak perlu. Menulis kode yang lebih abstrak daripada yang perlu sering dianggap sebagai kode bau yang disebut "generalisasi spekulatif" (juga disebutkan dalam posting).

Tetapi, jika saya mengikuti TDD, saya tidak dapat (dengan mudah) membuat tes ganda tanpa generalisasi spekulatif itu, baik dalam bentuk implementasi antarmuka atau opsi polimorfik kami yang lain, membuat kelas dapat diwariskan dan metodenya virtual.

Jadi, bagaimana kita merekonsiliasi pengorbanan ini? Apakah layak untuk menjadi spekulatif umum untuk memfasilitasi pengujian / TDD? Jika Anda menggunakan tes ganda, apakah ini dianggap sebagai implementasi kedua dan dengan demikian menjadikan generalitas tidak lagi spekulatif? Haruskah Anda mempertimbangkan kerangka kerja mengejek kelas berat yang memungkinkan mengejek kolaborator beton (mis. Mol versus Moq di dunia C #)? Atau, haruskah Anda menguji dengan kelas-kelas yang konkret dan menulis apa yang dapat dianggap sebagai tes "integrasi" sampai desain Anda secara alami membutuhkan polimorfisme?

Saya ingin sekali membaca pendapat orang lain tentang masalah ini - terima kasih sebelumnya atas pendapat Anda.

Erik Dietrich
sumber
Secara pribadi saya pikir entitas tidak boleh diejek. Saya hanya mengejek layanan, dan mereka memerlukan antarmuka dalam hal apa pun, karena kode domain kode biasanya tidak memiliki referensi ke kode di mana layanan diimplementasikan.
CodesInChaos
7
Kami pengguna bahasa yang diketik secara dinamis menertawakan chaffing Anda di rantai yang diberikan bahasa yang diketik secara statis kepada Anda. Ini adalah satu hal yang membuat pengujian unit lebih mudah dalam bahasa yang diketik secara dinamis, saya tidak perlu mengembangkan antarmuka untuk mengganti objek untuk tujuan pengujian.
Winston Ewert
Antarmuka tidak hanya digunakan untuk mempengaruhi sifat umum. Mereka digunakan untuk banyak tujuan, memisahkan kode Anda menjadi salah satu yang penting. Yang pada gilirannya membuat pengujian kode Anda lebih mudah.
Marjan Venema
@ WinstonEwert. Itu manfaat menarik dari pengetikan dinamis yang sebelumnya tidak saya anggap sebagai seseorang yang, seperti yang Anda tunjukkan, biasanya tidak bekerja dalam bahasa yang diketik secara dinamis.
Erik Dietrich
@CodeInChaos Saya belum mempertimbangkan perbedaan untuk keperluan pertanyaan ini, meskipun itu perbedaan yang masuk akal. Dalam hal ini, kami mungkin memikirkan kelas layanan / kerangka kerja dengan hanya satu implementasi (saat ini). Katakanlah saya memiliki database yang saya akses dengan DAO. Haruskah saya tidak menghubungkan DAO sampai saya memiliki model ketekunan sekunder? (Sepertinya itulah yang disiratkan oleh penulis posting blog)
Erik Dietrich

Jawaban:

14

Saya pergi dan membaca posting blog, dan saya setuju dengan banyak apa yang penulis katakan. Namun, jika Anda menulis kode menggunakan antarmuka untuk tujuan pengujian unit, saya akan mengatakan bahwa implementasi tiruan dari antarmuka adalah implementasi kedua Anda. Saya berpendapat bahwa itu benar-benar tidak menambah banyak kerumitan pada kode Anda, terutama jika pertukaran tidak melakukannya mengakibatkan kelas Anda menjadi sangat erat dan sulit untuk diuji.

Daniel Mann
sumber
3
Benar-benar tepat. Kode pengujian adalah bagian dari aplikasi karena Anda memerlukannya untuk mendapatkan desain, implementasi, pemeliharaan dll. Fakta bahwa Anda tidak mengirimkannya kepada pelanggan tidak relevan - jika ada implementasi kedua di ruang uji Anda, maka umumnya ada dan Anda harus mengakomodasinya.
Kilian Foth
1
Ini adalah jawaban yang saya temukan paling meyakinkan (dan @KilianFoth menambahkan bahwa apakah kode tersebut mengirimkan atau tidak implementasi kedua masih ada). Saya akan menunda menerima sedikit jawaban untuk melihat apakah ada orang lain yang ikut.
Erik Dietrich
Saya juga akan menambahkan, ketika Anda bergantung pada antarmuka dalam tes, generalitas tidak lagi spekulatif
Pete
"Tidak melakukannya" (menggunakan antarmuka) tidak secara otomatis menyebabkan kelas Anda menjadi sangat erat. Hanya saja tidak. Misalnya dalam .NET Framework, ada Streamkelas, tetapi tidak ada kopling ketat.
Luke Puplett
3

Menguji kode secara umum tidak mudah. Jika ya, kita sudah melakukannya sejak lama, dan tidak melakukan hal itu hanya dalam 10-15 tahun terakhir. Salah satu kesulitan terbesar selalu dalam menentukan bagaimana menguji kode yang telah ditulis secara kohesif, dan diperhitungkan dengan baik, dan dapat diuji tanpa melanggar enkapsulasi. Kepala sekolah BDD menyarankan agar kita memfokuskan hampir seluruhnya pada perilaku, dan dalam beberapa hal tampaknya menyarankan bahwa Anda tidak benar-benar perlu khawatir tentang perincian batin sedemikian besar, tetapi ini seringkali dapat membuat hal-hal yang cukup sulit untuk diuji jika ada. banyak metode pribadi yang melakukan "hal-hal" dengan cara yang sangat tersembunyi, karena dapat meningkatkan kompleksitas keseluruhan tes Anda untuk menangani semua hasil yang mungkin di tingkat yang lebih umum.

Mengejek dapat membantu sampai batas tertentu, tetapi sekali lagi cukup terfokus secara eksternal. Ketergantungan Injeksi juga dapat bekerja dengan sangat baik, sekali lagi dengan mengejek atau menguji ganda, tetapi ini juga dapat mengharuskan Anda mengekspos elemen baik melalui antarmuka, atau secara langsung, bahwa Anda mungkin lebih suka tetap tersembunyi - ini terutama benar jika Anda ingin memiliki tingkat keamanan paranoid yang bagus tentang kelas-kelas tertentu dalam sistem Anda.

Bagi saya, juri masih belum yakin apakah akan merancang kelas Anda agar lebih mudah diuji. Ini dapat menimbulkan masalah jika Anda merasa perlu memberikan tes baru sambil mempertahankan kode lawas. Saya menerima bahwa Anda harus dapat menguji segala sesuatu dalam suatu sistem, namun saya tidak suka gagasan mengekspos - bahkan secara tidak langsung - internal privat suatu kelas, hanya agar saya dapat menulis tes untuk mereka.

Bagi saya, solusinya selalu mengambil pendekatan yang cukup pragmatis, dan menggabungkan sejumlah teknik yang sesuai dengan setiap situasi tertentu. Saya menggunakan banyak tes ganda yang diwarisi untuk mengekspos sifat batin dan perilaku untuk pengujian saya. Saya mengejek segala sesuatu yang dapat dilampirkan ke kelas saya, dan di mana itu tidak akan mengganggu keamanan kelas saya, saya akan memberikan sarana untuk menimpa atau menyuntikkan perilaku untuk keperluan pengujian. Saya bahkan akan mempertimbangkan untuk menyediakan antarmuka yang lebih berdasarkan peristiwa jika itu akan membantu meningkatkan kemampuan untuk menguji kode

Di mana saya menemukan kode "yang tidak dapat diuji" , saya mencari tahu apakah saya bisa menolak untuk membuat hal-hal lebih dapat diuji. Di mana Anda memiliki banyak kode pribadi yang tersembunyi di balik layar, sering kali Anda akan menemukan kelas-kelas baru yang menunggu untuk dikeluarkan. Kelas-kelas ini dapat digunakan secara internal, tetapi seringkali dapat diuji secara independen dengan perilaku yang kurang pribadi, dan seringkali lapisan akses dan kompleksitasnya lebih sedikit. Namun satu hal yang harus saya hindari adalah menulis kode produksi dengan kode uji bawaan. Dapat tergoda untuk membuat " test lug " yang menghasilkan kengerian seperti itu if testing then ..., yang menunjukkan masalah pengujian yang tidak sepenuhnya didekonstruksi dan dipecahkan secara tidak lengkap.

Anda mungkin terbantu untuk membaca buku Pola Tes xUnit Gerard Meszaros , yang mencakup semua hal semacam ini dengan lebih rinci daripada yang bisa saya baca di sini. Saya mungkin tidak melakukan semua yang dia sarankan, tetapi itu membantu untuk memperjelas beberapa situasi tes rumit yang harus dihadapi. Pada akhirnya, Anda ingin dapat memenuhi persyaratan pengujian sambil tetap menerapkan desain pilihan Anda, dan membantu untuk memiliki pemahaman yang lebih baik tentang semua opsi agar dapat memutuskan dengan lebih baik di mana Anda mungkin perlu berkompromi.

S.Robins
sumber
1

Apakah bahasa yang Anda gunakan memiliki cara untuk "mengejek" objek untuk pengujian? Jika demikian, antarmuka yang mengganggu ini bisa hilang.

Pada catatan yang berbeda mungkin ada alasan untuk memiliki SimpleInterface dan satu-satunya ComplexThing yang mengimplementasikannya. Mungkin ada bagian dari ComplexThing yang Anda tidak ingin dapat diakses oleh pengguna SimpleInterface. Ini tidak selalu karena pembuat kode OO-ish yang terlalu bersemangat.

Saya akan menjauh sekarang dan membiarkan semua orang melompat pada fakta bahwa kode yang melakukan ini "baunya tidak enak" kepada mereka.


sumber
Ya, saya bekerja dalam bahasa (s) dengan kerangka kerja mengejek yang mendukung benda beton mengejek. Alat-alat ini membutuhkan berbagai tingkat melompat melalui lingkaran untuk melakukannya.
Erik Dietrich
0

Saya akan menjawab dalam dua bagian:

  1. Anda tidak perlu antarmuka jika Anda hanya tertarik pada pengujian. Saya menggunakan kerangka kerja mengejek untuk tujuan itu (di Jawa: Mockito atau easymock). Saya percaya bahwa kode yang Anda desain tidak boleh dimodifikasi untuk tujuan pengujian. Menulis kode yang dapat diuji, sama dengan menulis kode modular, jadi saya cenderung menulis kode modular (dapat diuji) dan hanya menguji kode antarmuka publik.

  2. Saya telah bekerja di sebuah proyek Java yang besar dan aku menjadi sangat yakin bahwa menggunakan read-only (hanya getter) interface adalah yang cara untuk pergi (silakan perhatikan bahwa aku penggemar besar dari kekekalan). Kelas implementasi mungkin memiliki setter, tapi itu detail implementasi yang tidak boleh diekspos ke lapisan luar. Dari perspektif lain saya lebih suka komposisi daripada warisan (modularitas, ingat?), Jadi antarmuka juga membantu di sini. Saya bersedia membayar biaya generalisasi spekulatif daripada menembak diri saya sendiri.

Gil Brandao
sumber
0

Saya telah melihat banyak keuntungan sejak saya mulai pemrograman lebih ke antarmuka di luar polimorfisme.

  • Ini memaksa saya untuk berpikir lebih keras tentang antarmuka kelas (ini adalah metode publik) sebelumnya dan bagaimana ia akan berinteraksi dengan antarmuka kelas lain.
  • Ini membantu saya menulis kelas yang lebih kecil yang lebih kompak dan mengikuti kepala tanggung jawab tunggal.
  • Lebih mudah untuk menguji kode saya
  • Kelas yang kurang statis / keadaan global karena kelas harus tingkat instance
  • Lebih mudah untuk mengintegrasikan dan merakit potongan bersama sebelum seluruh program siap
  • Ketergantungan injeksi, memisahkan konstruksi objek dari logika bisnis

Banyak orang akan setuju bahwa lebih banyak, kelas yang lebih kecil lebih baik daripada lebih sedikit, kelas yang lebih besar. Anda tidak harus terlalu fokus pada satu waktu dan setiap kelas memiliki tujuan yang jelas. Yang lain mungkin mengatakan Anda menambah kompleksitas dengan memiliki lebih banyak kelas.

Adalah baik untuk menggunakan alat untuk meningkatkan produktivitas, tetapi saya pikir hanya mengandalkan kerangka kerja Mock dan di tempat seperti membangun testibilitas dan modularitas langsung ke dalam kode akan menghasilkan kode kualitas yang lebih rendah dalam jangka panjang.

Semua dalam semua, saya percaya itu telah membantu saya menulis kode kualitas yang lebih tinggi dan manfaatnya jauh lebih besar daripada konsekuensinya.

Despertar
sumber