TDD Red-Green-Refactor dan jika / bagaimana menguji metode yang menjadi pribadi

91

Sejauh yang saya pahami, kebanyakan orang tampaknya setuju bahwa metode pribadi tidak boleh diuji secara langsung, melainkan melalui metode publik apa pun menyebutnya. Saya dapat melihat poin mereka, tetapi saya memiliki beberapa masalah dengan ini ketika saya mencoba untuk mengikuti "Tiga Hukum TDD", dan menggunakan siklus "Merah-hijau-refaktor". Saya pikir itu paling baik dijelaskan dengan sebuah contoh:

Saat ini, saya memerlukan program yang dapat membaca file (berisi data yang dipisahkan tab) dan memfilter semua kolom yang berisi data non-numerik. Saya kira mungkin ada beberapa alat sederhana yang sudah tersedia untuk melakukan ini, tetapi saya memutuskan untuk mengimplementasikannya sendiri dari awal, terutama karena saya pikir itu bisa menjadi proyek yang bagus dan bersih bagi saya untuk mendapatkan beberapa latihan dengan TDD.

Jadi, pertama, saya "memakai topi merah", yaitu, saya perlu tes yang gagal. Saya pikir, saya akan membutuhkan metode yang menemukan semua bidang non-numerik dalam satu baris. Jadi saya menulis tes sederhana, tentu saja gagal untuk mengkompilasi segera, jadi saya mulai menulis fungsi itu sendiri, dan setelah beberapa siklus bolak-balik (merah / hijau) saya memiliki fungsi kerja dan tes lengkap.

Selanjutnya, saya melanjutkan dengan fungsi, "collectNonNumericColumns" yang membaca file, satu baris setiap kali, dan memanggil "findNonNumericFields" saya - fungsi pada setiap baris untuk mengumpulkan semua kolom yang akhirnya harus dihapus. Beberapa siklus merah-hijau, dan saya selesai, memiliki lagi, fungsi kerja dan tes lengkap.

Sekarang, saya pikir saya harus refactor. Karena metode "findNonNumericFields" saya dirancang hanya karena saya pikir saya akan membutuhkannya ketika mengimplementasikan "collectNonNumericColumns", menurut saya masuk akal jika membiarkan "findNonNumericFields" menjadi pribadi. Namun, itu akan merusak tes pertama saya, karena mereka tidak lagi memiliki akses ke metode yang mereka uji.

Jadi, saya berakhir dengan metode pribadi, dan serangkaian tes yang mengujinya. Karena begitu banyak orang menyarankan agar metode pribadi tidak diuji, rasanya seperti saya mengecat diri saya sendiri di sudut sini. Tapi di mana tepatnya aku gagal?

Saya kira saya bisa mulai pada level yang lebih tinggi, menulis tes yang menguji apa yang akhirnya akan menjadi metode publik saya (yaitu, findAndFilterOutAllNonNumericalColumns), tetapi itu terasa agak berlawanan dengan seluruh titik TDD (setidaknya menurut Paman Bob) : Bahwa Anda harus beralih secara konstan antara tes penulisan dan kode produksi, dan bahwa pada titik waktu mana saja, semua tes Anda bekerja dalam satu menit terakhir atau lebih. Karena jika saya memulai dengan menulis tes untuk metode publik, akan ada beberapa menit (atau jam, atau bahkan berhari-hari dalam kasus yang sangat kompleks) sebelum saya mendapatkan semua detail dalam metode pribadi untuk bekerja sehingga tes menguji publik metode berlalu.

Jadi, apa yang harus dilakukan? Apakah TDD (dengan siklus red-green-refactor) tidak kompatibel dengan metode pribadi? Atau ada kesalahan dalam desain saya?

Henrik Berg
sumber
2
Entah kedua fungsi ini cukup berbeda untuk menjadi unit yang berbeda - dalam hal ini metode privat mungkin harus di kelas mereka sendiri - atau mereka adalah unit yang sama dalam hal ini saya tidak melihat mengapa Anda menulis tes untuk perilaku internal ke unit. Mengenai paragraf kedua dari belakang, saya tidak melihat konflik. Mengapa Anda perlu menulis seluruh metode pribadi yang kompleks untuk lulus satu test case? Mengapa tidak mengusirnya secara bertahap melalui metode publik, atau mulai dengan itu sebaris kemudian ekstrak itu?
Ben Aaronson
26
Mengapa orang mengambil idiom dan klise dari buku dan blog pemrograman sebagai pedoman aktual tentang bagaimana program berada di luar jangkauan saya.
AK_
7
Saya tidak suka TDD karena alasan ini: jika Anda berada di area baru maka Anda akan melakukan banyak pekerjaan tambahan sambil mencoba mencari tahu bagaimana arsitekturnya dan bagaimana beberapa hal bekerja. Di sisi lain: jika Anda berada di area yang sudah Anda alami maka akan ada manfaat untuk menulis tes pertama selain mengganggu Anda karena intellisense tidak mengerti mengapa Anda menulis kode yang tidak dapat dikompilasi. Saya penggemar berpikir tentang desain, menulis dan kemudian mengujinya.
Jeroen Vannevel
1
"kebanyakan orang tampaknya setuju bahwa metode pribadi tidak boleh diuji secara langsung" - tidak, uji metode secara langsung jika masuk akal untuk melakukannya. Sembunyikan itu seolah- privateolah masuk akal untuk melakukannya.
Osa

Jawaban:

44

Unit

Saya pikir saya dapat menunjukkan dengan tepat di mana masalah dimulai:

Saya pikir, saya akan membutuhkan metode yang menemukan semua bidang non-numerik dalam satu baris.

Ini harus segera diikuti dengan bertanya pada diri sendiri, "Apakah itu akan menjadi unit yang dapat diuji terpisah gatherNonNumericColumnsatau bagian dari unit yang sama?"

Jika jawabannya " ya, pisahkan ", maka tindakan Anda sederhana: metode itu harus dipublikasikan pada kelas yang sesuai, sehingga dapat diuji sebagai satu unit. Mental Anda adalah sesuatu seperti "Saya perlu mencoba mengusir satu metode dan saya juga perlu menguji mengusir metode lain"

Dari apa yang Anda katakan, Anda menduga bahwa jawabannya adalah " tidak, bagian dari yang sama ". Pada titik ini, rencana Anda seharusnya tidak lagi untuk sepenuhnya menulis dan menguji findNonNumericFields kemudian menulis gatherNonNumericColumns. Alih-alih, itu harus ditulis gatherNonNumericColumns. Untuk saat ini, findNonNumericFieldsseharusnya hanya menjadi bagian dari tujuan yang ada dalam pikiran Anda ketika Anda memilih test case merah Anda berikutnya dan melakukan refactoring Anda. Kali ini mentalitas Anda adalah "Saya perlu menguji mengusir satu metode, dan sementara saya melakukannya saya harus ingat bahwa implementasi saya yang sudah selesai mungkin akan mencakup metode lain ini".


Menjaga siklus pendek

Melakukan hal di atas seharusnya tidak mengarah ke masalah yang Anda jelaskan di paragraf kedua dari belakang Anda:

Karena jika saya memulai dengan menulis tes untuk metode publik, akan ada beberapa menit (atau jam, atau bahkan berhari-hari dalam kasus yang sangat kompleks) sebelum saya mendapatkan semua detail dalam metode pribadi untuk bekerja sehingga tes menguji publik metode berlalu.

Tidak ada titik teknik ini mengharuskan Anda untuk menulis tes merah yang hanya akan berubah menjadi hijau ketika Anda menerapkan keseluruhan findNonNumericFieldsdari awal. Jauh lebih mungkin, findNonNumericFieldsakan dimulai karena beberapa kode sejalan dengan metode publik yang Anda uji, yang akan dibangun selama beberapa siklus dan akhirnya diekstraksi selama refactoring.


Peta jalan

Untuk memberikan perkiraan peta jalan untuk contoh khusus ini, saya tidak tahu persis kasus pengujian yang Anda gunakan, tetapi katakan Anda menulis gatherNonNumericColumnssebagai metode publik Anda. Maka kemungkinan besar kasus uji akan sama dengan yang Anda tulis findNonNumericFields, masing-masing menggunakan tabel dengan hanya satu baris. Ketika skenario satu-baris itu dilaksanakan sepenuhnya, dan Anda ingin menulis tes untuk memaksa Anda mengekstrak metode ini, Anda akan menulis kasus dua baris yang mengharuskan Anda menambahkan iterasi.

Ben Aaronson
sumber
2
Saya pikir ini jawabannya di sini. Mengadopsi TDD dalam lingkungan OOP, saya sering mengalami kesulitan mengatasi insting bottom-up saya sendiri. Ya, fungsinya harus kecil, tapi itu setelah refactoring. Sebelumnya, mereka bisa menjadi monolit besar. +1
João Mendes
2
@ JoãoMendes Baiklah, saya tidak yakin Anda harus mencapai status monolit besar sebelum melakukan refactoring, terutama pada siklus RGR yang sangat singkat. Tapi ya, di dalam unit yang dapat diuji, bekerja dari bawah ke atas dapat menyebabkan masalah yang dijelaskan OP.
Ben Aaronson
1
OK, saya pikir saya mengerti di mana kesalahannya sekarang. Terima kasih banyak untuk Anda semua (menandai jawaban ini sebagai jawaban, tetapi sebagian besar jawaban lainnya juga sama-sama membantu)
Henrik Berg
66

Banyak orang berpikir bahwa pengujian unit berbasis metode; ini bukan. Itu harus didasarkan pada unit terkecil yang masuk akal. Untuk sebagian besar hal ini berarti kelas adalah apa yang harus Anda uji secara keseluruhan. Bukan metode individual di atasnya.

Sekarang jelas Anda akan memanggil metode di kelas, tetapi Anda harus menganggap tes tersebut berlaku untuk objek kotak hitam yang Anda miliki, jadi Anda harus dapat melihat bahwa operasi logis apa pun yang disediakan oleh kelas Anda; ini adalah hal-hal yang perlu Anda uji. Jika kelas Anda sangat besar sehingga operasi logis terlalu kompleks, maka Anda memiliki masalah desain yang harus diperbaiki terlebih dahulu.

Kelas dengan seribu metode mungkin tampak dapat diuji, tetapi jika Anda hanya menguji setiap metode secara individual, Anda tidak benar-benar menguji kelas tersebut. Beberapa kelas mungkin perlu berada dalam keadaan tertentu sebelum metode dipanggil, misalnya kelas jaringan yang memerlukan koneksi yang diatur sebelum mengirim data. Metode pengiriman data tidak dapat dianggap independen dari seluruh kelas.

Jadi, Anda harus melihat bahwa metode pribadi tidak relevan dengan pengujian. Jika Anda tidak dapat menggunakan metode pribadi Anda dengan memanggil antarmuka publik dari kelas Anda, maka metode pribadi itu tidak berguna dan tidak akan digunakan.

Saya pikir banyak orang mencoba mengubah metode pribadi menjadi unit yang dapat diuji karena tampaknya mudah untuk menjalankan tes untuk mereka, tetapi ini mengambil granularity tes terlalu jauh. Martin Fowler berkata

Meskipun saya mulai dengan gagasan unit menjadi kelas, saya sering mengambil banyak kelas yang terkait erat dan memperlakukan mereka sebagai satu unit

yang membuat banyak akal untuk sistem berorientasi objek, objek yang dirancang untuk menjadi unit. Jika Anda ingin menguji metode individual, mungkin Anda harus membuat sistem prosedural seperti C, atau kelas yang seluruhnya terdiri dari fungsi statis.

gbjbaanb
sumber
14
Bagi saya kelihatannya jawaban ini benar-benar mengabaikan pendekatan TDD dalam pertanyaan OP. Itu hanya pengulangan dari "tidak menguji metode pribadi" mantra, tetapi tidak menjelaskan bagaimana TDD - yang adalah benar-benar metode berbasis - mungkin bekerja dengan pendekatan uji unit berbasis non-metode.
Doc Brown
6
@DocBrown tidak, itu menjawab dia sepenuhnya dengan mengatakan "jangan over-granualise" unit Anda dan membuat hidup sulit untuk diri sendiri. TDD bukan berbasis metode, itu adalah unit berbasis di mana unit adalah apa pun yang masuk akal. Jika Anda memiliki pustaka C, maka ya setiap unit akan menjadi fungsi. Jika Anda memiliki kelas, unit adalah objek. Seperti yang dikatakan Fowler, terkadang sebuah unit adalah beberapa kelas yang terkait erat. Saya pikir banyak orang menganggap pengujian unit sebagai metode demi metode hanya karena beberapa alat bodoh menghasilkan bertopik berdasarkan metode.
gbjbaanb
3
@ gbjbaanb: mencoba menyarankan tes yang memungkinkan OP mengimplementasikan "mengumpulkan bidang non-numerik dalam satu baris" dengan menggunakan TDD murni terlebih dahulu, tanpa memiliki antarmuka publik dari kelas yang ingin ditulisnya sudah ditulis.
Doc Brown
8
Saya harus setuju dengan @DocBrown di sini. Masalah si penanya bukanlah dia menginginkan pengujian granularitas lebih dari yang dapat dia capai tanpa menguji metode pribadi. Itu karena dia mencoba mengikuti pendekatan TDD yang ketat dan - tanpa perencanaan seperti itu - yang membawanya ke dinding di mana dia tiba-tiba menemukan dia memiliki banyak tes untuk apa yang seharusnya menjadi metode pribadi. Jawaban ini tidak membantu. Ini jawaban yang bagus untuk beberapa pertanyaan, tapi bukan yang ini.
Ben Aaronson
7
@ Matthew: Kesalahannya adalah bahwa ia menulis fungsi di tempat pertama. Idealnya, ia seharusnya menulis metode publik sebagai kode spageti kemudian mengubahnya menjadi fungsi pribadi dalam siklus refactor - bukan menandainya sebagai pribadi dalam siklus refactor.
Slebetman
51

Fakta bahwa metode pengumpulan data Anda cukup kompleks untuk mendapatkan tes dan cukup terpisah dari tujuan utama Anda untuk menjadi metode mereka sendiri daripada bagian dari beberapa poin loop ke solusi: jadikan metode ini tidak pribadi, tetapi anggota dari beberapa kelas lain yang menyediakan fungsi pengumpulan / penyaringan / tabulasi.

Kemudian Anda menulis tes untuk aspek data-munging bodoh dari kelas pembantu (misalnya "membedakan angka dari karakter") di satu tempat, dan tes untuk tujuan utama Anda (misalnya "mendapatkan angka penjualan") di tempat lain, dan Anda tidak harus mengulangi tes penyaringan dasar dalam tes untuk logika bisnis normal Anda.

Secara umum, jika kelas Anda yang melakukan satu hal berisi kode yang luas untuk melakukan hal lain yang diperlukan untuk, tetapi terpisah dari, tujuan utamanya, kode itu harus hidup di kelas lain dan dipanggil melalui metode publik. Seharusnya tidak disembunyikan di sudut-sudut pribadi kelas yang hanya secara tidak sengaja berisi kode itu. Ini meningkatkan kemampuan uji dan pemahaman pada saat yang sama.

Kilian Foth
sumber
Ya aku setuju denganmu. Tapi saya punya masalah dengan pernyataan pertama Anda, baik bagian "cukup kompleks" dan bagian "cukup terpisah". Mengenai "cukup kompleks": Saya mencoba melakukan siklus merah-hijau yang cepat, yang berarti bahwa saya hanya dapat kode paling banyak satu menit atau lebih pada satu waktu sebelum beralih ke pengujian (atau sebaliknya). Itu berarti bahwa tes saya akan sangat halus. Saya pikir itu adalah salah satu kelebihan dengan TDD, tapi mungkin saya sudah berlebihan, sehingga menjadi kerugian.
Henrik Berg
Mengenai "cukup terpisah": Saya telah belajar (lagi dari unclebob) bahwa fungsi harus kecil, dan mereka harus lebih kecil dari itu. Jadi pada dasarnya saya mencoba membuat fungsi 3-4 baris. Jadi kurang lebih semua fungsi dipisahkan menjadi metode mereka sendiri, tidak peduli seberapa kecil dan sederhana.
Henrik Berg
Bagaimanapun, saya merasa seperti aspek data-munging (mis. FindNonNumericFields) harus benar-benar pribadi. Dan jika saya memisahkannya ke kelas lain, saya harus tetap mempublikasikannya, jadi saya tidak mengerti maksudnya.
Henrik Berg
6
@HenrikBerg berpikir mengapa Anda memiliki objek di tempat pertama - mereka bukan cara yang nyaman untuk fungsi grup, tetapi unit mandiri yang membuat sistem kompleks lebih mudah untuk dikerjakan. Oleh karena itu, Anda harus berpikir untuk menguji kelas sebagai suatu hal.
gbjbaanb
@ gbjbaanb Saya berpendapat bahwa keduanya sama dan sama.
RubberDuck
29

Secara pribadi, saya merasa Anda melangkah jauh ke dalam pola pikir implementasi ketika Anda menulis tes. Anda mengira Anda akan memerlukan metode tertentu. Tetapi apakah Anda benar-benar membutuhkan mereka untuk melakukan apa yang seharusnya dilakukan kelas? Apakah kelas akan gagal jika seseorang datang dan refactored mereka secara internal? Jika Anda menggunakan kelas (dan itu harus menjadi pola pikir penguji menurut pendapat saya), Anda benar-benar tidak peduli jika ada metode eksplisit untuk memeriksa angka.

Anda harus menguji antarmuka publik dari suatu kelas. Implementasi pribadi bersifat pribadi karena suatu alasan. Ini bukan bagian dari antarmuka publik karena tidak diperlukan dan dapat berubah. Ini detail implementasi.

Jika Anda menulis tes terhadap antarmuka publik, Anda tidak akan pernah benar-benar mendapatkan masalah yang Anda hadapi. Entah Anda dapat membuat kasus uji untuk antarmuka publik yang mencakup metode pribadi Anda (hebat) atau tidak. Dalam hal ini, mungkin sudah saatnya untuk berpikir keras tentang metode pribadi dan mungkin membuang semuanya jika tidak bisa dicapai.

tidak ada
sumber
1
"Detail implementasi" adalah hal-hal seperti "apakah saya menggunakan XOR atau variabel sementara untuk bertukar int antar variabel". Metode yang dilindungi / pribadi memiliki kontrak, seperti yang lainnya. Mereka mengambil input, bekerja dengannya, dan menghasilkan beberapa output, di bawah kendala tertentu. Apa pun dengan kontrak pada akhirnya harus diuji - tidak harus bagi mereka yang menggunakan perpustakaan Anda, tetapi bagi mereka yang mempertahankan dan memodifikasinya setelah Anda. Hanya karena itu bukan "publik" tidak berarti itu bukan bagian dari sebuah API.
Knetic
11

Anda tidak melakukan TDD berdasarkan apa yang Anda harapkan akan dilakukan oleh kelas secara internal.

Kasing uji Anda harus didasarkan pada apa yang harus dilakukan oleh kelas / fungsionalitas / program terhadap dunia eksternal. Dalam contoh Anda, apakah pengguna akan pernah memanggil kelas pembaca Anda denganfind all the non-numerical fields in a line?

Jika jawabannya "tidak" maka itu adalah ujian yang buruk untuk menulis di tempat pertama. Anda ingin menulis tes tentang fungsionalitas pada level kelas / antarmuka - bukan level "apa yang harus diterapkan oleh metode kelas untuk membuatnya berfungsi", yang merupakan pengujian Anda.

Alur TDD adalah:

  • merah (apa yang dilakukan kelas / objek / fungsi / dll ke dunia luar)
  • hijau (tulis kode minimal untuk membuat fungsi dunia eksternal ini berfungsi)
  • refactor (apa kode yang lebih baik untuk membuat ini bekerja)

Ini BUKAN untuk melakukan "karena saya akan membutuhkan X di masa depan sebagai metode pribadi, izinkan saya menerapkannya dan mengujinya terlebih dahulu." Jika Anda menemukan diri Anda melakukan ini, Anda melakukan tahapan "merah" secara tidak benar. Ini tampaknya menjadi masalah Anda di sini.

Jika Anda sering menulis tes untuk metode yang menjadi metode pribadi, Anda melakukan salah satu dari beberapa hal:

  • Tidak cukup memahami kasus penggunaan antarmuka / tingkat publik Anda dengan baik untuk menulis tes untuk mereka
  • Secara dramatis mengubah desain Anda dan melakukan refactoring pada beberapa tes (yang mungkin merupakan hal yang baik, tergantung pada apakah fungsionalitas tersebut diuji dalam tes yang lebih baru)
enderland
sumber
9

Anda mengalami kesalahpahaman umum dengan pengujian secara umum.

Kebanyakan orang yang baru melakukan pengujian mulai berpikir seperti ini:

  • tulis tes untuk fungsi F
  • mengimplementasikan F
  • tulis tes untuk fungsi G
  • mengimplementasikan G menggunakan panggilan ke F
  • menulis tes untuk suatu fungsi H
  • mengimplementasikan H menggunakan panggilan ke G

dan seterusnya.

Masalahnya di sini adalah bahwa Anda sebenarnya tidak memiliki unit test untuk fungsi H. Tes yang seharusnya menguji H sebenarnya menguji H, G dan F pada saat yang sama.

Untuk mengatasi ini, Anda harus menyadari bahwa unit yang diuji tidak boleh saling bergantung, melainkan pada antarmuka mereka . Dalam kasus Anda, di mana unit-unit adalah fungsi-fungsi sederhana antarmuka hanyalah tanda tangan panggilan mereka. Karena itu Anda harus menerapkan G sedemikian rupa, sehingga dapat digunakan dengan fungsi apa pun yang memiliki tanda tangan yang sama dengan F.

Bagaimana tepatnya ini dapat dilakukan tergantung pada bahasa pemrograman Anda. Dalam banyak bahasa, Anda dapat meneruskan fungsi (atau menunjuk ke sana) sebagai argumen ke fungsi lain. Ini akan memungkinkan Anda untuk menguji setiap fungsi secara terpisah.

initcrash
sumber
3
Saya berharap bisa memilih ini lebih banyak lagi. Saya hanya akan meringkasnya sebagai, Anda belum merancang solusi Anda dengan benar.
Justin Ohms
Dalam bahasa seperti C, ini masuk akal, tetapi untuk bahasa OO di mana unit umumnya harus kelas (dengan metode publik dan pribadi) maka Anda harus menguji kelas, tidak setiap metode pribadi secara terpisah. Mengisolasi kelas Anda, ya. Mengisolasi metode di setiap kelas, tidak.
gbjbaanb
8

Tes yang Anda tulis selama Pengembangan Didorong Uji seharusnya memastikan bahwa suatu kelas mengimplementasikan API publiknya dengan benar, sementara secara bersamaan memastikan bahwa API publik mudah untuk diuji dan digunakan.

Anda dapat menggunakan metode pribadi untuk mengimplementasikan API itu, tetapi tidak perlu membuat tes melalui TDD - fungsionalitas akan diuji karena API publik akan bekerja dengan benar.

Sekarang anggaplah metode pribadi Anda cukup rumit sehingga layak untuk diuji mandiri - tetapi tidak masuk akal sebagai bagian dari API publik kelas asli Anda. Nah, ini mungkin berarti bahwa mereka seharusnya benar-benar menjadi metode publik pada kelas lain - kelas yang dimanfaatkan kelas asli Anda dalam implementasinya sendiri.

Dengan hanya menguji API publik, Anda membuatnya jauh lebih mudah untuk memodifikasi detail implementasi di masa mendatang. Tes tidak membantu hanya akan mengganggu Anda nanti ketika harus ditulis ulang untuk mendukung beberapa refactoring elegan yang baru saja Anda temukan.

Bill Michell
sumber
4

Saya pikir jawaban yang tepat adalah kesimpulan Anda tentang memulai dengan metode publik. Anda akan mulai dengan menulis tes yang memanggil metode itu. Itu akan gagal sehingga Anda membuat metode dengan nama yang tidak menghasilkan apa-apa. Maka Anda mungkin benar tes yang memeriksa nilai kembali.

(Saya tidak sepenuhnya jelas tentang apa fungsi Anda. Apakah itu mengembalikan string dengan konten file dengan nilai-nilai non-numerik dihapus?)

Jika metode Anda mengembalikan string maka Anda memeriksa nilai kembali itu. Jadi Anda terus membangunnya.

Saya pikir segala sesuatu yang terjadi dalam metode pribadi harus dalam metode publik di beberapa titik selama proses Anda, dan kemudian hanya pindah ke metode pribadi sebagai bagian dari langkah refactoring. Refactoring tidak memerlukan tes yang gagal, sejauh yang saya tahu. Anda hanya perlu gagal dalam tes saat menambahkan fungsionalitas. Anda hanya perlu menjalankan tes setelah refactor untuk memastikan semuanya lulus.

Matt Dyer
sumber
3

rasanya seperti saya telah melukis diri saya di sudut sini. Tapi di mana tepatnya aku gagal?

Ada pepatah lama.

Ketika Anda gagal merencanakan, Anda berencana untuk gagal.

Orang-orang tampaknya berpikir bahwa ketika Anda TDD, Anda hanya duduk, menulis tes, dan desain hanya akan terjadi secara ajaib. Ini tidak benar. Anda harus memiliki rencana tingkat tinggi. Saya menemukan bahwa saya mendapatkan hasil terbaik dari TDD ketika saya mendesain antarmuka (API publik) terlebih dahulu. Secara pribadi, saya membuat aktual interfaceyang mendefinisikan kelas terlebih dahulu.

terkesiap saya menulis beberapa "kode" sebelum menulis tes apa pun! Ya tidak. Saya tidak melakukannya. Saya menulis kontrak yang harus diikuti, sebuah desain . Saya menduga Anda bisa mendapatkan hasil yang sama dengan menuliskan diagram UML di kertas grafik. Intinya, Anda harus punya rencana. TDD bukan lisensi untuk pergi tanpa peretasan di sepotong kode.

Saya benar-benar merasa seperti "Test First" adalah keliru. Desain Pertama, lalu uji.

Tentu saja, silakan ikuti saran yang diberikan orang lain tentang mengekstraksi lebih banyak kelas dari kode Anda. Jika Anda merasa perlu untuk menguji internal kelas, ekstrak internal tersebut ke dalam unit yang mudah diuji dan menyuntikkannya.

Bebek karet
sumber
2

Ingatlah bahwa tes juga dapat di refactored! Jika Anda membuat metode pribadi, Anda mengurangi API publik, dan karenanya sangat diterima untuk membuang beberapa tes yang sesuai untuk "fungsionalitas yang hilang" (AKA mengurangi kompleksitas).

Orang lain mengatakan metode pribadi Anda akan dipanggil sebagai bagian dari tes API Anda yang lain, atau itu tidak akan terjangkau dan karenanya dapat dihapus. Bahkan, hal-hal lebih halus jika kita berpikir tentang jalur eksekusi .

Misalnya, jika kita memiliki metode publik yang melakukan pembagian, kita mungkin ingin menguji jalur yang menghasilkan pembagian-oleh-nol. Jika kita menjadikan metode ini privat, kita mendapatkan pilihan: kita dapat mempertimbangkan jalur pembagian-oleh-nol, atau kita dapat menghilangkan jalur itu dengan mempertimbangkan bagaimana metode itu dipanggil oleh metode lain.

Dengan cara ini, kami dapat membuang beberapa tes (mis. Divide-by-zero) dan refactor yang lain dalam hal API publik yang tersisa. Tentu saja, di dunia yang ideal tes yang ada mengurus semua jalur yang tersisa, tetapi kenyataan selalu kompromi;)

Warbo
sumber
1
Sementara jawaban lainnya benar karena metode pribadi seharusnya tidak ditulis dalam siklus merah, manusia membuat kesalahan. Dan ketika Anda sudah menempuh jalan kesalahan cukup jauh, ini adalah solusi yang tepat.
Slebetman
2

Ada saat-saat ketika metode pribadi dapat dijadikan metode umum untuk kelas lain.

Misalnya, Anda mungkin memiliki metode pribadi yang tidak aman utas dan meninggalkan kelas dalam keadaan sementara. Metode-metode ini dapat dipindahkan ke kelas terpisah yang dipegang secara pribadi oleh kelas pertama Anda. Jadi jika kelas Anda adalah Antrian, Anda bisa memiliki kelas InternalQueue yang memiliki metode publik, dan kelas Antrian menyimpan instance InternalQueue secara pribadi. Ini memungkinkan Anda menguji antrian internal, dan juga menjelaskan apa operasi individu pada InternalQueue.

(Ini paling jelas ketika Anda membayangkan tidak ada kelas Daftar, dan jika Anda mencoba mengimplementasikan fungsi Daftar sebagai metode pribadi di kelas yang menggunakannya.)

Thomas Andrews
sumber
2
"Ada kalanya metode pribadi bisa dijadikan metode umum untuk kelas lain." Saya tidak bisa cukup menekankan itu. Kadang-kadang, metode pribadi hanyalah kelas lain yang berteriak untuk identitasnya sendiri.
0

Saya heran mengapa bahasa Anda hanya memiliki dua tingkat privasi, sepenuhnya publik dan sepenuhnya pribadi.

Bisakah Anda mengatur metode non-publik Anda dapat diakses paket atau semacamnya? Kemudian masukkan tes Anda dalam paket yang sama dan nikmati pengujian inner yang bukan bagian dari antarmuka publik. Sistem build Anda akan mengecualikan tes saat membangun biner rilis.

Tentu saja kadang-kadang Anda perlu memiliki metode yang benar-benar pribadi, tidak dapat diakses oleh apa pun kecuali kelas yang menentukan. Saya harap semua metode seperti ini sangat kecil. Secara umum, menjaga metode yang kecil (misalnya di bawah 20 baris) sangat membantu: pengujian, pemeliharaan dan hanya memahami kode menjadi lebih mudah.

9000
sumber
3
Mengubah pengubah akses metode hanya untuk menjalankan tes adalah situasi di mana ekor menggoyangkan anjing. Saya pikir menguji bagian dalam unit hanya membuat sulit untuk refactor nanti. Pengujian antarmuka publik, sebaliknya, sangat bagus karena berfungsi sebagai "kontrak" untuk sebuah unit.
scriptin
Anda tidak perlu mengubah tingkat akses suatu metode. Yang ingin saya katakan adalah bahwa Anda memiliki tingkat akses menengah yang memungkinkan untuk menulis kode tertentu, termasuk tes, lebih mudah, tanpa membuat kontrak publik. Tentu saja Anda harus menguji antarmuka publik, tetapi juga terkadang bermanfaat untuk menguji beberapa pekerjaan internal secara terpisah.
9000
0

Saya kadang-kadang menabrak metode pribadi untuk dilindungi untuk memungkinkan pengujian berbutir lebih baik (lebih ketat daripada API publik yang terbuka). Ini harus menjadi pengecualian (mudah-mudahan sangat jarang) daripada aturan, tetapi dapat membantu dalam kasus-kasus tertentu tertentu yang mungkin Anda temui. Juga, itu adalah sesuatu yang Anda tidak ingin mempertimbangkan sama sekali ketika membangun API publik, lebih dari "cheat" yang dapat digunakan pada perangkat lunak penggunaan internal dalam situasi yang jarang terjadi.

Brian Knoblauch
sumber
0

Saya sudah mengalami ini dan merasakan sakit Anda.

Solusi saya adalah:

berhentilah mengobati tes seperti membangun monolit.

Ingatlah bahwa ketika Anda telah menulis serangkaian tes, katakanlah 5, untuk mengusir beberapa fungsi, Anda tidak harus menyimpan semua tes ini , terutama ketika ini menjadi bagian dari sesuatu yang lain.

Misalnya saya sering punya:

  • tes tingkat rendah 1
  • kode untuk memenuhi itu
  • tes tingkat rendah 2
  • kode untuk memenuhi itu
  • tes tingkat rendah 3
  • kode untuk memenuhi itu
  • tes tingkat rendah 4
  • kode untuk memenuhi itu
  • tes tingkat rendah 5
  • kode untuk memenuhi itu

jadi aku punya

  • tes tingkat rendah 1
  • tes tingkat rendah 2
  • tes tingkat rendah 3
  • tes tingkat rendah 4
  • tes tingkat rendah 5

Namun jika sekarang saya menambahkan fungsi level yang lebih tinggi yang menyebutnya, yang memiliki banyak tes, sekarang saya mungkin dapat mengurangi tes tingkat rendah itu menjadi:

  • tes tingkat rendah 1
  • tes tingkat rendah 5

Iblis ada dalam perincian dan kemampuan untuk melakukannya akan tergantung pada keadaan.

Michael Durrant
sumber
-2

Apakah matahari berputar mengelilingi bumi atau bumi mengelilingi matahari? Menurut Einstein jawabannya adalah ya, atau keduanya karena kedua model hanya berbeda berdasarkan sudut pandang, demikian juga enkapsulasi dan pengembangan yang digerakkan oleh tes hanya dalam konflik karena kami pikir keduanya. Kami duduk di sini seperti Galileo dan paus, saling menghina satu sama lain: bodoh, tidakkah Anda melihat bahwa metode pribadi juga perlu diuji; sesat, jangan hancurkan enkapsulasi! Demikian juga ketika kita menyadari bahwa kebenaran itu lebih agung daripada yang kita duga, kita dapat mencoba sesuatu seperti merangkum pengujian untuk antarmuka pribadi sehingga pengujian untuk antarmuka publik tidak merusak enkapsulasi.

Coba ini: tambahkan dua metode, satu yang tidak memiliki input tetapi hanya mengembalikan jumlah tes pribadi dan satu yang mengambil nomor tes sebagai parameter dan mengembalikan lulus / gagal.

Hildred
sumber
1
Penghinaan yang dipilih digunakan oleh Galileo dan Paus, bukan dengan jawaban untuk pertanyaan ini.
Hildred