TDD: Mengejek benda yang dipasangkan dengan erat

10

Terkadang benda hanya perlu dipasangkan dengan erat. Sebagai contoh, suatu CsvFilekelas mungkin perlu bekerja erat dengan CsvRecordkelas (atau ICsvRecordantarmuka).

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 ICsvRecordejekan atau bertopik daripada contoh yang sebenarnya CsvRecord.

Namun setelah mencoba pendekatan ini, saya perhatikan bahwa mengejek CsvRecordkelas bisa menjadi sedikit berbulu. Yang membawa saya ke salah satu dari dua kesimpulan:

  1. Sulit untuk menulis tes unit! Itu bau kode! Refactor!
  2. Mengejek setiap ketergantungan hanya tidak masuk akal.

Ketika saya mengganti tiruan saya dengan CsvRecordcontoh 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?

Phil
sumber
1
Saya pikir itu adalah kesalahpahaman umum bahwa "unit" dalam "unit test" harus harus satu kelas. Saya pikir contoh Anda menunjukkan kasus di mana mungkin lebih baik bahwa dua kelas tersebut membentuk satu unit. Tapi jangan salah paham, saya setuju sepenuhnya dengan jawaban Robert Harvey.
Doc Brown

Jawaban:

11

Jika Anda benar-benar membutuhkan koordinasi antara kedua kelas itu, tulislah sebuah CsvCoordinatorkelas yang merangkum dua kelas Anda, dan ujilah itu.

Namun, saya membantah anggapan yang CsvRecordtidak dapat diuji secara independen. CsvRecordpada dasarnya adalah kelas DTO , bukan? Itu hanya kumpulan bidang, dengan mungkin beberapa metode pembantu. Dan CsvRecorddapat digunakan dalam konteks lain selain CsvFile; Anda dapat memiliki koleksi atau array CsvRecords, misalnya.

Tes CsvRecorddulu. Pastikan ia lulus semua tesnya. Kemudian, lanjutkan dan gunakan CsvRecorddengan CsvFilekelas Anda selama tes. Gunakan itu sebagai rintisan / tiruan yang telah diuji; isi dengan data uji yang relevan, berikan kepada CsvFile, dan tulis kasus uji Anda untuk itu.

Robert Harvey
sumber
1
Ya, CsvRecord pasti dapat diuji secara independen. Masalahnya adalah bahwa jika sesuatu rusak di CsvRecord, itu akan menyebabkan tes CsvData gagal. Tapi saya pikir itu bukan masalah besar.
Phil
1
Saya pikir Anda ingin itu terjadi. :)
Robert Harvey
1
@RobertHarvey: secara teori, ini bisa menjadi masalah jika CsvRecord dan CsvFile menjadi kelas yang cukup kompleks, dan jika tes istirahat untuk CsvFile, sekarang Anda tidak tahu dengan segera apakah itu masalah di CsvFile atau CsvRecord. Tapi saya kira itu lebih merupakan kasus hipotetis - jika saya memiliki tugas pemrograman kelas-kelas seperti untuk program dunia nyata, saya akan melakukannya persis seperti yang Anda gambarkan.
Doc Brown
2
@ Phil: Jika CsvRecordrusak, maka jelas CsvDatagagal; tetapi ini OK, karena Anda menguji CsvRecorddulu, dan jika itu gagal, CsvFiletes Anda tidak ada artinya. Anda masih dapat membedakan antara kesalahan masuk CsvRecorddan masuk CsvFile.
tdammers
5

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 CsvRecordkebanyakan 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 bernama CsvRecordyang melakukan banyak perhitungan rumit.

Tetapi jika CsvRecordtidak 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.

Dawood ibn Kareem
sumber
+1. Setiap tes yang hasilnya tergantung pada kebenaran perilaku lebih dari satu objek adalah tes integrasi, bukan tes unit. Anda harus mengejek salah satu objek ini untuk mendapatkan tes unit nyata. Ini tidak berlaku untuk objek tanpa perilaku nyata di dalamnya - dengan hanya pengambil dan setter misalnya.
guillaume31
1

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.

Telastyn
sumber
0

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.

Matthew Flynn
sumber
1
-1, kopling ketat tidak selalu berarti ketergantungan siklik, itu kesalahpahaman. Dalam contoh, CsvFile adalah erat digabungkan ke CsvRecord(tapi tidak dengan cara putaran lain). OP bertanya apakah ide yang bagus untuk menguji CsvFiledengan memisahkannya dari CsvRecordvia ICsvRecord, bukan sebaliknya.
Doc Brown
2
@DocBrown: Apakah koplingnya kencang atau tidak adalah masalah seberapa besar CsvFiletergantung pada cara kerja bagian dalam CsvRecord, 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 dalamnya CsvFile. Memperkenalkan antarmuka hanya agar Anda bisa mengatakan Anda telah mengurangi kopling itu konyol.
tdammers
0

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 CsvRecordsebagai 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.

Karl Bielefeldt
sumber