Ketika melakukan tes unit dengan cara yang "tepat", yaitu mematikan setiap panggilan publik dan mengembalikan nilai atau cemoohan yang telah ditetapkan, saya merasa seperti saya tidak benar-benar menguji apa pun. Saya benar-benar melihat kode saya dan membuat contoh berdasarkan aliran logika melalui metode publik saya. Dan setiap kali implementasinya berubah, saya harus pergi dan mengubah tes-tes itu, sekali lagi, tidak benar-benar merasa bahwa saya menyelesaikan sesuatu yang bermanfaat (baik itu jangka menengah atau panjang). Saya juga melakukan tes integrasi (termasuk jalur yang tidak bahagia) dan saya tidak terlalu keberatan dengan waktu pengujian yang meningkat. Dengan itu, saya merasa seperti saya benar-benar menguji untuk regresi, karena mereka telah menangkap banyak, sementara semua unit tes lakukan menunjukkan kepada saya bahwa implementasi metode publik saya berubah, yang saya sudah tahu.
Unit testing adalah topik yang luas, dan saya merasa seperti saya yang tidak memahami sesuatu di sini. Apa keuntungan yang menentukan dari pengujian unit vs pengujian integrasi (tidak termasuk waktu overhead)?
sumber
Jawaban:
Ini kedengarannya seperti metode yang Anda uji perlu beberapa instance kelas lain (yang harus Anda tiru), dan memanggil beberapa metode sendiri.
Jenis kode ini memang sulit untuk unit-test, karena alasan yang Anda uraikan.
Apa yang saya temukan bermanfaat adalah untuk membagi kelas-kelas tersebut menjadi:
Maka kelas-kelas dari 1. mudah untuk unit-test, karena mereka hanya menerima nilai dan mengembalikan hasilnya. Dalam kasus yang lebih kompleks, kelas-kelas ini mungkin perlu melakukan panggilan sendiri, tetapi mereka hanya akan memanggil kelas dari 2. (dan tidak secara langsung memanggil misalnya fungsi database), dan kelas-kelas dari 2. mudah untuk diejek (karena mereka hanya buka bagian-bagian dari sistem terbungkus yang Anda butuhkan).
Kelas-kelas dari 2. dan 3. biasanya tidak dapat diuji unit secara bermakna (karena mereka tidak melakukan sesuatu yang berguna pada mereka sendiri, mereka hanya kode "lem"). OTOH, kelas-kelas ini cenderung relatif sederhana (dan sedikit), sehingga kelas-kelas tersebut harus dicakup secara memadai oleh tes integrasi.
Sebuah contoh
Satu kelas
Katakanlah Anda memiliki kelas yang mengambil harga dari database, menerapkan beberapa diskon dan kemudian memperbarui database.
Jika Anda memiliki ini semua dalam satu kelas, Anda harus memanggil fungsi DB, yang sulit untuk diejek. Dalam pseudocode:
Ketiga langkah akan membutuhkan akses DB, jadi banyak mengejek (kompleks), yang kemungkinan akan rusak jika kode atau struktur DB berubah.
Berpisah
Anda terbagi menjadi tiga kelas: Penghitungan Harga, Penentuan Harga, Aplikasi.
PriceCalculation hanya melakukan perhitungan aktual, dan diberikan nilai yang dibutuhkannya. Aplikasi mengikat semuanya:
Seperti itu:
Akhirnya, mungkin PriceCalculation harus melakukan panggilan basis data sendiri. Misalnya karena hanya PriceCalculation yang tahu data mana yang dibutuhkan, sehingga tidak dapat diambil terlebih dahulu oleh App. Kemudian Anda bisa memberikan instance PriceRepository (atau kelas repositori lain), yang disesuaikan dengan kebutuhan PriceCalculation. Kelas ini kemudian perlu diejek, tetapi ini akan sederhana, karena antarmuka PriceRepository sederhana, misalnya
PriceRepository.getPrice(articleNo, contractType)
. Yang paling penting, antarmuka PriceRepository mengisolasi PriceCalculation dari database, sehingga perubahan pada skema DB atau organisasi data tidak mungkin untuk mengubah antarmuka, dan karenanya dapat merusak mock.sumber
Itu dikotomi yang salah.
Pengujian unit dan pengujian integrasi memiliki dua tujuan yang serupa tetapi berbeda. Tujuan dari pengujian unit adalah untuk memastikan metode Anda bekerja. Secara praktis, unit test memastikan bahwa kode memenuhi kontrak yang digariskan oleh unit test. Ini jelas dalam cara unit test dirancang: mereka secara khusus menyatakan apa yang seharusnya dilakukan oleh kode , dan menyatakan bahwa kode melakukan itu.
Tes integrasi berbeda. Tes integrasi melatih interaksi antar komponen perangkat lunak. Anda dapat memiliki komponen perangkat lunak yang lulus semua tes mereka dan masih gagal tes integrasi karena mereka tidak berinteraksi dengan benar.
Namun, jika ada keuntungan yang menentukan untuk pengujian unit, inilah: pengujian unit jauh lebih mudah untuk dibuat, dan membutuhkan jauh lebih sedikit waktu dan usaha daripada tes integrasi. Ketika digunakan dengan benar, unit test mendorong pengembangan kode "testable", yang berarti hasil akhir akan lebih andal, lebih mudah dipahami, dan lebih mudah untuk dipelihara. Kode yang dapat diuji memiliki karakteristik tertentu, seperti API yang koheren, perilaku berulang, dan mengembalikan hasil yang mudah ditegaskan.
Tes integrasi lebih sulit dan lebih mahal, karena Anda sering membutuhkan ejekan yang rumit, pengaturan yang rumit, dan pernyataan yang sulit. Pada tingkat integrasi sistem tertinggi, bayangkan mencoba mensimulasikan interaksi manusia dalam UI. Seluruh sistem perangkat lunak dikhususkan untuk otomatisasi semacam itu. Dan itu adalah otomatisasi yang kita kejar; pengujian manusia tidak dapat diulang, dan tidak skala seperti pengujian otomatis.
Akhirnya, pengujian integrasi tidak memberikan jaminan tentang cakupan kode. Berapa banyak kombinasi loop kode, kondisi dan cabang yang Anda uji dengan tes integrasi Anda? Apakah kamu benar-benar tahu? Ada alat yang dapat Anda gunakan dengan unit test dan metode yang sedang diuji yang akan memberi tahu Anda berapa banyak cakupan kode yang Anda miliki, dan apa kompleksitas cyclomatic dari kode Anda. Tetapi mereka hanya benar-benar bekerja dengan baik di tingkat metode, di mana unit test hidup.
Jika tes Anda berubah setiap kali Anda refactor, itu masalah yang berbeda. Tes unit seharusnya tentang mendokumentasikan apa yang dilakukan perangkat lunak Anda, membuktikan bahwa ia melakukan itu, dan kemudian membuktikannya melakukannya lagi ketika Anda menolak implementasi yang mendasarinya. Jika API Anda berubah, atau Anda perlu metode Anda untuk berubah sesuai dengan perubahan dalam desain sistem, itulah yang seharusnya terjadi. Jika itu sering terjadi, pertimbangkan untuk menulis tes Anda terlebih dahulu, sebelum Anda menulis kode. Ini akan memaksa Anda untuk memikirkan keseluruhan arsitektur, dan memungkinkan Anda untuk menulis kode dengan API yang sudah ada.
Jika Anda menghabiskan banyak waktu menulis tes unit untuk kode sepele seperti
maka Anda harus menguji kembali pendekatan Anda. Unit testing seharusnya menguji perilaku, dan tidak ada perilaku pada baris kode di atas. Namun, Anda telah membuat ketergantungan pada kode Anda di suatu tempat, karena properti itu hampir pasti akan dirujuk ke tempat lain dalam kode Anda. Alih-alih melakukan itu, pertimbangkan menulis metode yang menerima properti yang dibutuhkan sebagai parameter:
Sekarang metode Anda tidak memiliki ketergantungan pada sesuatu di luar dirinya sendiri, dan sekarang lebih dapat diuji, karena sepenuhnya mandiri. Memang, Anda tidak akan selalu bisa melakukan ini, tetapi itu memindahkan kode Anda ke arah yang lebih dapat diuji, dan kali ini Anda sedang menulis unit test untuk perilaku aktual.
sumber
Tes unit dengan mengejek adalah untuk memastikan implementasi kelas sudah benar. Anda mengejek antarmuka publik dari dependensi kode yang Anda uji. Dengan cara ini Anda memiliki kontrol atas segala sesuatu di luar kelas dan yakin bahwa tes yang gagal adalah karena sesuatu yang internal ke kelas dan bukan di salah satu objek lainnya.
Anda juga menguji perilaku kelas yang diuji, bukan implementasinya. Jika Anda memperbaiki kode (membuat metode internal baru, dll) pengujian unit seharusnya tidak gagal. Tetapi jika Anda mengubah apa yang dilakukan metode publik maka tes pasti gagal karena Anda telah mengubah perilaku.
Sepertinya Anda menulis tes setelah Anda menulis kode, cobalah menulis tes terlebih dahulu. Cobalah menguraikan perilaku yang harus dimiliki kelas dan kemudian menulis jumlah kode minimum untuk membuat tes lulus.
Pengujian unit dan pengujian integrasi berguna untuk memastikan kualitas kode Anda. Tes unit memeriksa setiap komponen secara terpisah. Dan tes integrasi memastikan bahwa semua komponen berinteraksi dengan benar. Saya ingin memiliki kedua jenis di ruang tes saya.
Tes unit telah membantu saya dalam pengembangan saya karena saya dapat fokus pada satu bagian aplikasi sekaligus. Mengejek komponen yang belum saya buat. Mereka juga bagus untuk regresi, karena mereka mendokumentasikan bug dalam logika yang saya temukan (bahkan dalam tes unit).
MEMPERBARUI
Membuat tes yang hanya memastikan bahwa metode yang dipanggil memiliki nilai karena Anda memastikan bahwa metode yang benar-benar dipanggil. Khususnya jika Anda menulis tes Anda terlebih dahulu, Anda memiliki daftar periksa metode yang perlu terjadi. Karena kode ini cukup prosedural, Anda tidak perlu banyak memeriksa selain itu metode dipanggil. Anda melindungi kode untuk perubahan di masa mendatang. Ketika Anda perlu memanggil satu metode sebelum yang lain. Atau metode selalu dipanggil meskipun metode awal melempar pengecualian.
Tes untuk metode ini mungkin tidak pernah berubah atau dapat berubah hanya ketika Anda mengubah metode. Kenapa ini hal yang buruk? Ini membantu memperkuat menggunakan tes. Jika Anda harus memperbaiki tes setelah mengubah kode, Anda akan terbiasa mengubah tes dengan kode.
sumber
Saya mengalami pertanyaan serupa - sampai saya menemukan kekuatan pengujian komponen. Singkatnya, mereka sama dengan tes unit kecuali bahwa Anda tidak mengejek secara default tetapi menggunakan objek nyata (idealnya melalui injeksi ketergantungan).
Dengan begitu, Anda dapat dengan cepat membuat tes yang kuat dengan cakupan kode yang baik. Tidak perlu memperbarui tiruan Anda sepanjang waktu. Mungkin sedikit kurang tepat daripada tes unit dengan 100% mengejek, tetapi waktu dan uang yang Anda simpan mengkompensasinya. Satu-satunya hal yang Anda benar-benar perlu gunakan untuk mengejek atau perlengkapan adalah penyimpanan backend atau layanan eksternal.
Sebenarnya, mengejek yang berlebihan adalah anti-pola: TDD Anti-Patterns dan Mock adalah kejahatan .
sumber
Meskipun op sudah menandai jawaban, saya hanya menambahkan 2 sen saya di sini.
Dan juga dalam menanggapi
Ada yang membantu tapi tidak persis apa yang ditanyakan OP:
Tes Unit berhasil tetapi masih ada bug?
dari sedikit pengalaman saya pada suite pengujian, saya mengerti bahwa Tes Unit selalu menguji fungsi tingkat metode paling dasar dari sebuah kelas. Menurut pendapat saya, setiap metode baik publik, swasta atau internal layak untuk memiliki unit test khusus. Bahkan dalam pengalaman saya baru-baru ini saya memiliki metode publik yang memanggil metode pribadi kecil lainnya. Jadi, ada dua pendekatan:
Jika Anda berpikir secara logis, tujuan memiliki metode pribadi adalah: metode publik utama menjadi terlalu besar atau berantakan. Untuk mengatasi ini, Anda harus melakukan refactor secara bijaksana dan membuat potongan kode kecil yang layak menjadi metode pribadi terpisah yang pada gilirannya membuat metode publik utama Anda tidak terlalu besar. Anda menolak dengan mengingat bahwa metode pribadi ini dapat digunakan kembali nanti. Mungkin ada kasus di mana tidak ada metode publik lainnya tergantung pada metode pribadi itu, tetapi siapa yang tahu tentang masa depan.
Mempertimbangkan kasus ketika metode pribadi digunakan kembali oleh banyak metode publik lainnya.
Jadi, jika saya memilih pendekatan 1: Saya akan menduplikasi tes Unit dan itu akan menjadi rumit, karena Anda memiliki jumlah Tes Unit untuk setiap cabang metode publik maupun metode pribadi.
Jika saya memilih pendekatan 2: kode yang ditulis untuk unit test akan relatif lebih sedikit, dan akan lebih mudah untuk diuji.
Mempertimbangkan kasus ketika metode pribadi tidak digunakan kembali. Tidak ada gunanya menulis unit test terpisah untuk metode itu.
Adapun seperti Tes Integrasi yang bersangkutan, mereka cenderung lengkap dan lebih tinggi. Mereka akan memberi tahu Anda bahwa dengan diberi input, semua kelas Anda harus sampai pada kesimpulan akhir ini. Untuk memahami lebih lanjut tentang kegunaan pengujian integrasi, silakan lihat tautan yang disebutkan.
sumber