Untuk memperluas sedikit pada judul, saya mencoba untuk mendapatkan beberapa kesimpulan tentang apakah perlu atau tidak untuk secara eksplisit mendeklarasikan (yaitu menyuntikkan) fungsi murni di mana beberapa fungsi atau kelas lain bergantung.
Apakah kode yang diberikan kurang dapat diuji atau lebih buruk dirancang jika menggunakan fungsi murni tanpa meminta mereka? Saya ingin sampai pada kesimpulan tentang masalah ini, untuk segala jenis fungsi murni dari fungsi sederhana, asli (misalnya max()
, min()
- terlepas dari bahasa) ke kebiasaan, yang lebih rumit yang pada gilirannya mungkin secara implisit tergantung pada fungsi murni lainnya.
Perhatian yang biasa adalah bahwa jika beberapa kode hanya menggunakan dependensi secara langsung, Anda tidak akan dapat mengujinya secara terpisah, yaitu pada saat yang sama Anda akan menguji semua hal yang Anda bawa secara diam-diam bersama Anda. Tapi ini menambahkan beberapa boilerplate jika Anda harus melakukannya untuk setiap fungsi kecil, jadi saya ingin tahu apakah ini masih berlaku untuk fungsi murni, dan mengapa atau mengapa tidak.
Jawaban:
Tidak, itu tidak buruk
Tes yang Anda tulis seharusnya tidak peduli bagaimana kelas atau fungsi tertentu diimplementasikan. Sebaliknya, harus memastikan bahwa mereka menghasilkan hasil yang Anda inginkan terlepas dari bagaimana tepatnya mereka diterapkan.
Sebagai contoh, pertimbangkan kelas berikut:
Anda ingin menguji fungsi 'Putaran' untuk memastikan bahwa ia mengembalikan apa yang Anda harapkan, terlepas dari bagaimana sebenarnya fungsi tersebut diimplementasikan. Anda mungkin akan menulis tes yang mirip dengan yang berikut ini;
Dari sudut pandang pengujian, selama fungsi Coord2d :: Round () Anda mengembalikan apa yang Anda harapkan, Anda tidak peduli bagaimana penerapannya.
Dalam beberapa kasus, menyuntikkan fungsi murni bisa menjadi cara yang sangat brilian untuk membuat kelas lebih dapat diuji atau diperluas.
Namun, dalam kebanyakan kasus, seperti fungsi Coord2d :: Round () di atas, tidak ada kebutuhan nyata untuk menyuntikkan fungsi min / max / floor / ceil / trunc. Fungsi ini dirancang untuk membulatkan nilainya ke bilangan bulat terdekat. Tes yang Anda tulis harus memeriksa apakah fungsi melakukan ini.
Terakhir, jika Anda ingin menguji kode yang bergantung pada implementasi kelas / fungsi, Anda dapat melakukannya dengan hanya menulis tes untuk dependensi.
Misalnya, jika fungsi Coord2d :: Round () diimplementasikan seperti ...
Jika Anda ingin menguji fungsi 'lantai', Anda dapat melakukannya di unit test terpisah.
sumber
Dari sudut pandang pengujian - Tidak, tetapi hanya dalam kasus fungsi murni , ketika fungsi mengembalikan output yang sama untuk input yang sama.
Bagaimana Anda menguji unit yang menggunakan fungsi yang disuntikkan secara eksplisit?
Anda akan menyuntikkan fungsi mocked (palsu) dan memeriksa fungsi yang dipanggil dengan argumen yang benar.
Tetapi karena kita memiliki fungsi murni , yang selalu mengembalikan hasil yang sama untuk argumen yang sama - memeriksa argumen input sama dengan memeriksa hasil keluaran.
Dengan fungsi murni Anda tidak perlu mengonfigurasi dependensi / status tambahan.
Sebagai manfaat tambahan Anda akan mendapatkan enkapsulasi yang lebih baik, Anda akan menguji perilaku unit yang sebenarnya.
Dan sangat penting dari sudut pandang pengujian - Anda akan bebas untuk merefactor kode internal unit tanpa mengubah tes - misalnya Anda memutuskan untuk menambahkan satu argumen lagi untuk fungsi murni - dengan fungsi yang disuntikkan secara eksplisit (dipermainkan dalam tes) Anda harus ubah konfigurasi tes, di mana dengan fungsi yang digunakan secara implisit Anda tidak melakukan apa pun.
Saya bisa membayangkan situasi ketika Anda perlu menyuntikkan fungsi murni - adalah ketika Anda ingin menawarkan bagi konsumen untuk mengubah perilaku unit.
Untuk metode di atas Anda mengharapkan perhitungan diskon dapat diubah oleh konsumen, jadi Anda harus mengecualikan pengujian logikanya dari unit test untuk
CalcualteTotalPrice
metode.sumber
Dalam dunia ideal pemrograman SOLID OO Anda akan menyuntikkan setiap perilaku eksternal. Dalam praktiknya Anda akan selalu menggunakan beberapa fungsi sederhana (atau tidak begitu sederhana) secara langsung.
Jika Anda menghitung maksimum satu set angka, mungkin akan terlalu banyak untuk menyuntikkan
max
fungsi dan mengejeknya dalam unit test. Biasanya Anda tidak peduli tentang memisahkan kode Anda dari implementasi spesifikmax
dan mengujinya secara terpisah.Jika Anda melakukan sesuatu yang kompleks seperti menemukan jalur biaya minimum dalam grafik maka Anda sebaiknya menyuntikkan implementasi konkret untuk fleksibilitas dan mengejeknya dalam unit test untuk kinerja yang lebih baik. Dalam hal ini mungkin layak karya decoupling algoritma pencarian jalur dari kode yang menggunakannya.
Tidak ada jawaban untuk "fungsi murni", Anda harus memutuskan di mana harus menarik garis. Ada terlalu banyak faktor yang terlibat dalam keputusan ini. Pada akhirnya Anda harus mempertimbangkan manfaat dari masalah yang diberikan decoupling kepada Anda, dan itu tergantung pada Anda dan konteks Anda.
Saya melihat di komentar bahwa Anda bertanya tentang perbedaan antara kelas injeksi (sebenarnya Anda menyuntikkan objek) dan fungsi. Tidak ada perbedaan, hanya fitur bahasa yang membuatnya terlihat berbeda. Dari sudut pandang abstrak memanggil fungsi tidak berbeda dengan memanggil metode beberapa objek dan Anda menyuntikkan (atau tidak) fungsi untuk alasan yang sama Anda menyuntikkan (atau tidak) objek: untuk memisahkan perilaku itu dari kode panggilan dan memiliki kemampuan untuk menggunakan implementasi perilaku yang berbeda dan memutuskan di tempat lain implementasi apa yang digunakan.
Jadi pada dasarnya kriteria apa pun yang Anda anggap valid untuk injeksi dependensi Anda dapat menggunakannya terlepas dari ketergantungannya adalah objek atau fungsi. Mungkin ada beberapa nuansa tergantung pada bahasa (yaitu jika Anda menggunakan java ini bukan masalah).
sumber
max()
(sebagai lawan dari hal lain)?Fungsi murni tidak memengaruhi testabilitas kelas karena sifat-sifatnya:
Ini berarti bahwa mereka secara kasar berada di ranah yang sama dengan metode pribadi, yang sepenuhnya berada di bawah kendali kelas, dengan bonus tambahan bahkan tidak tergantung pada keadaan instance saat ini (yaitu "ini"). Kelas pengguna "mengontrol" output karena sepenuhnya mengontrol input.
Contoh yang Anda berikan, maks () dan min (), bersifat deterministik. Namun, random () dan currentTime () adalah prosedur, bukan fungsi murni (mereka bergantung pada / memodifikasi keadaan dunia keluar dari pita), misalnya. Dua yang terakhir ini harus disuntikkan untuk memungkinkan pengujian.
sumber