Terkadang benda hanya perlu dipasangkan dengan erat. Sebagai contoh, suatu CsvFile
kelas mungkin perlu bekerja erat dengan CsvRecord
kelas (atau ICsvRecord
antarmuka).
Namun dari apa yang saya pelajari di masa lalu, salah satu prinsip utama pengembangan yang digerakkan oleh tes adalah "Jangan pernah menguji lebih dari satu kelas pada satu waktu." Berarti Anda harus menggunakan ICsvRecord
ejekan atau bertopik daripada contoh yang sebenarnya CsvRecord
.
Namun setelah mencoba pendekatan ini, saya perhatikan bahwa mengejek CsvRecord
kelas bisa menjadi sedikit berbulu. Yang membawa saya ke salah satu dari dua kesimpulan:
- Sulit untuk menulis tes unit! Itu bau kode! Refactor!
- Mengejek setiap ketergantungan hanya tidak masuk akal.
Ketika saya mengganti tiruan saya dengan CsvRecord
contoh yang sebenarnya , segalanya berjalan lebih lancar. Ketika mencari-cari pemikiran orang lain, saya sengaja menemukan posting blog ini , yang tampaknya mendukung # 2 di atas. Untuk benda-benda yang secara alami terikat erat, kita tidak perlu terlalu khawatir tentang mengejek.
Apakah saya keluar jalur? Apakah ada kerugian untuk asumsi # 2 di atas? Haruskah saya benar-benar berpikir tentang refactoring desain saya?
Jawaban:
Jika Anda benar-benar membutuhkan koordinasi antara kedua kelas itu, tulislah sebuah
CsvCoordinator
kelas yang merangkum dua kelas Anda, dan ujilah itu.Namun, saya membantah anggapan yang
CsvRecord
tidak dapat diuji secara independen.CsvRecord
pada dasarnya adalah kelas DTO , bukan? Itu hanya kumpulan bidang, dengan mungkin beberapa metode pembantu. DanCsvRecord
dapat digunakan dalam konteks lain selainCsvFile
; Anda dapat memiliki koleksi atau arrayCsvRecord
s, misalnya.Tes
CsvRecord
dulu. Pastikan ia lulus semua tesnya. Kemudian, lanjutkan dan gunakanCsvRecord
denganCsvFile
kelas Anda selama tes. Gunakan itu sebagai rintisan / tiruan yang telah diuji; isi dengan data uji yang relevan, berikan kepadaCsvFile
, dan tulis kasus uji Anda untuk itu.sumber
CsvRecord
rusak, maka jelasCsvData
gagal; tetapi ini OK, karena Anda mengujiCsvRecord
dulu, dan jika itu gagal,CsvFile
tes Anda tidak ada artinya. Anda masih dapat membedakan antara kesalahan masukCsvRecord
dan masukCsvFile
.Alasan untuk menguji satu kelas pada suatu waktu adalah bahwa Anda tidak ingin tes untuk satu kelas memiliki ketergantungan pada perilaku kelas kedua. Itu berarti bahwa jika tes Anda untuk Kelas A melakukan salah satu fungsionalitas Kelas B, maka Anda harus mengejek Kelas B untuk menghapus ketergantungan pada fungsionalitas tertentu di dalam Kelas B.
Menurut saya, kelas seperti
CsvRecord
kebanyakan untuk menyimpan data - ini bukan kelas dengan terlalu banyak fungsinya sendiri. Artinya, mungkin memiliki konstruktor, getter, setter, tetapi tidak ada metode dengan logika substansial nyata. Tentu saja, saya menebak di sini - mungkin Anda telah menulis sebuah kelas bernamaCsvRecord
yang melakukan banyak perhitungan rumit.Tetapi jika
CsvRecord
tidak memiliki logika sendiri, tidak ada yang bisa diperoleh dengan mengejeknya. Ini benar-benar hanya pepatah lama - "jangan mengejek nilai objek" .Jadi ketika mempertimbangkan apakah akan mengejek kelas tertentu (untuk ujian kelas yang berbeda), Anda harus memperhitungkan berapa banyak dari logikanya sendiri yang dimiliki oleh kelas tersebut, dan seberapa banyak dari logika itu akan dieksekusi selama tes Anda.
sumber
No. 2 baik-baik saja. Hal-hal dapat, dan harus digabungkan secara erat jika konsep mereka digabungkan secara ketat. Ini harus jarang, dan umumnya dihindari, tetapi dalam contoh yang Anda berikan itu masuk akal.
sumber
Kelas "digabungkan" saling bergantung satu sama lain. Ini seharusnya tidak menjadi kasus dalam apa yang Anda gambarkan - CsvRecord seharusnya tidak benar-benar peduli tentang CsvFile yang memuatnya, jadi ketergantungannya hanya berjalan satu arah. Itu bagus, dan tidak kopling ketat.
Lagipula, jika sebuah kelas berisi nama String variabel, Anda tidak akan mengklaim itu secara ketat digabungkan ke String, bukan?
Jadi, unit menguji CsvRecord untuk perilaku yang diinginkan.
Kemudian gunakan kerangka kerja mengejek (Mockito hebat) untuk menguji apakah unit Anda berinteraksi dengan objek yang bergantung padanya dengan benar. Perilaku yang ingin Anda uji, benar-benar - adalah CsvFile menangani CsvRcords dengan cara yang diharapkan. Cara kerja dalam CvsRecord seharusnya tidak masalah - begitulah cara CvsFile berkomunikasi dengannya.
Akhirnya, TDD tidak hanya tentang unit test. Anda tentu dapat (dan harus) mulai dengan tes fungsional yang melihat perilaku fungsional tentang bagaimana komponen Anda yang lebih besar bekerja - yaitu, cerita atau skenario pengguna Anda. Tes unit Anda menetapkan harapan dan memverifikasi bagian, tes fungsional melakukan hal yang sama untuk keseluruhan.
sumber
CsvFile
adalah erat digabungkan keCsvRecord
(tapi tidak dengan cara putaran lain). OP bertanya apakah ide yang bagus untuk mengujiCsvFile
dengan memisahkannya dariCsvRecord
viaICsvRecord
, bukan sebaliknya.CsvFile
tergantung pada cara kerja bagian dalamCsvRecord
, yaitu, jumlah asumsi yang dimiliki file tentang rekaman. Antarmuka membantu mendokumentasikan dan menegakkan asumsi tersebut (atau lebih tepatnya tidak adanya asumsi lain), tetapi jumlah kopling tetap sama, kecuali bahwa dengan antarmuka, Anda dapat mengaitkan kelas rekaman yang berbeda ke dalamnyaCsvFile
. Memperkenalkan antarmuka hanya agar Anda bisa mengatakan Anda telah mengurangi kopling itu konyol.Sebenarnya ada dua pertanyaan di sini. Yang pertama adalah jika ada situasi di mana mengejek suatu objek tidak disarankan. Tidak diragukan lagi itu benar, seperti yang ditunjukkan oleh jawaban-jawaban luar biasa lainnya. Pertanyaan kedua adalah apakah kasus khusus Anda adalah salah satu dari situasi itu. Pada pertanyaan itu saya tidak yakin.
Mungkin alasan paling umum untuk tidak mengejek kelas adalah jika itu adalah kelas nilai. Namun, Anda harus melihat alasan di balik aturan tersebut. Ini bukan karena kelas yang diolok-olok akan menjadi buruk entah bagaimana, itu karena pada dasarnya akan identik dengan yang asli. Jika itu masalahnya, pengujian unit Anda tidak akan lebih mudah menggunakan kelas asli.
Sangat mungkin bahwa kode Anda adalah salah satu pengecualian langka di mana refactoring tidak akan membantu, tetapi Anda hanya boleh mendeklarasikannya setelah upaya refactoring yang rajin tidak berhasil. Bahkan pengembang berpengalaman dapat kesulitan melihat alternatif untuk desain mereka sendiri. Jika Anda tidak dapat memikirkan cara yang mungkin untuk memperbaikinya, mintalah seseorang yang berpengalaman untuk melihatnya kembali.
Sebagian besar orang tampaknya menganggap Anda
CsvRecord
sebagai kelas nilai. Cobalah membuatnya menjadi satu. Buatlah abadi jika Anda bisa. Jika Anda memiliki dua objek dengan pointer satu sama lain, hapus salah satu dari mereka dan cari cara untuk membuatnya bekerja. Cari tempat untuk membagi kelas dan fungsi. Tempat terbaik untuk membagi kelas mungkin tidak selalu cocok dengan tata letak fisik file. Coba membalikkan hubungan orang tua / anak di kelas. Mungkin Anda memerlukan kelas terpisah untuk membaca dan menulis file csv. Mungkin Anda perlu kelas terpisah untuk menangani file I / O dan antarmuka ke lapisan atas. Ada banyak hal yang harus dicoba sebelum menyatakannya tidak bisa dihindari.sumber