Seperti yang saya mengerti, titik unit test adalah untuk menguji unit kode secara terpisah . Ini berarti, bahwa:
- Mereka tidak boleh dipecah oleh perubahan kode yang tidak terkait di tempat lain dalam basis kode.
- Hanya satu tes unit yang harus dipecahkan oleh bug di unit yang diuji, yang bertentangan dengan tes integrasi (yang mungkin pecah dalam tumpukan).
Semua ini menyiratkan, bahwa setiap ketergantungan luar dari unit yang diuji, harus dipermainkan. Dan maksud saya semua dependensi luar , tidak hanya "lapisan luar" seperti jaringan, sistem file, database, dll.
Ini mengarah pada kesimpulan logis, bahwa hampir setiap unit tes perlu diejek . Di sisi lain, pencarian Google cepat tentang mengejek mengungkapkan banyak artikel yang mengklaim bahwa "mengejek adalah bau kode" dan sebagian besar (walaupun tidak sepenuhnya) harus dihindari.
Sekarang, ke pertanyaan.
- Bagaimana seharusnya tes unit ditulis dengan benar?
- Di mana tepatnya garis antara mereka dan tes integrasi berada?
Perbarui 1
Silakan pertimbangkan kode pseudo berikut:
class Person {
constructor(calculator) {}
calculate(a, b) {
const sum = this.calculator.add(a, b);
// do some other stuff with the `sum`
}
}
Dapatkah tes yang menguji Person.calculate
metode tanpa mengejek Calculator
ketergantungan (diberikan, bahwa Calculator
kelas ringan yang tidak mengakses "dunia luar") dianggap sebagai unit test?
sumber
Jawaban:
Martin Fowler pada Unit Test
Apa yang ditulis Kent Beck dalam Test Driven Development, By Example
Klaim "titik unit test" yang diberikan adalah "akan sangat bergantung pada definisi" unit test "apa yang dipertimbangkan.
Jika perspektif Anda adalah bahwa program Anda terdiri dari banyak unit kecil yang saling bergantung satu sama lain, dan jika Anda membatasi diri pada gaya yang menguji setiap unit secara terpisah, maka banyak tes ganda merupakan kesimpulan yang tidak dapat dihindari.
Saran yang bertentangan yang Anda lihat berasal dari orang-orang yang beroperasi di bawah serangkaian asumsi yang berbeda.
Misalnya, jika Anda menulis tes untuk mendukung pengembang selama proses refactoring, dan membagi satu unit menjadi dua adalah refactoring yang harus didukung, maka sesuatu harus diberikan. Mungkin tes semacam ini membutuhkan nama yang berbeda? Atau mungkin kita membutuhkan pemahaman yang berbeda tentang "unit".
Anda mungkin ingin membandingkan:
Saya pikir itu pertanyaan yang salah untuk ditanyakan; lagi-lagi argumen tentang label , ketika saya percaya apa yang sebenarnya kita pedulikan adalah properti .
Ketika saya memperkenalkan perubahan pada kode, saya tidak peduli tentang isolasi tes - saya sudah tahu bahwa "kesalahan" ada di suatu tempat di tumpukan saat ini dari suntingan yang belum diverifikasi. Jika saya sering menjalankan tes, maka saya membatasi kedalaman tumpukan itu, dan menemukan kesalahan itu sepele (dalam kasus ekstrim, tes dijalankan setelah setiap edit - kedalaman maksimal tumpukan adalah satu). Tetapi menjalankan tes bukanlah tujuannya - ini adalah gangguan - jadi ada nilai dalam mengurangi dampak gangguan. Salah satu cara untuk mengurangi gangguan adalah untuk memastikan bahwa tes cepat ( Gary Bernhardt menyarankan 300 ms , tapi saya belum menemukan cara melakukannya dalam keadaan saya).
Jika memohon
Calculator::add
tidak secara signifikan meningkatkan waktu yang diperlukan untuk menjalankan tes (atau properti penting lainnya untuk kasus penggunaan ini), maka saya tidak akan repot menggunakan uji ganda - itu tidak memberikan manfaat yang lebih besar daripada biaya .Perhatikan dua asumsi di sini: manusia sebagai bagian dari evaluasi biaya, dan tumpukan pendek dari perubahan yang belum diverifikasi dalam evaluasi manfaat. Dalam keadaan di mana kondisi tersebut tidak berlaku, nilai "isolasi" berubah sedikit.
Lihat juga Hot Lava , oleh Harry Percival.
sumber
Dengan meminimalkan efek samping dalam kode Anda.
Mengambil contoh kode Anda, jika
calculator
misalnya berbicara ke API web, maka Anda dapat membuat tes rapuh yang mengandalkan kemampuan untuk berinteraksi dengan API web itu, atau Anda membuat tiruannya. Namun, jika ini merupakan fungsi perhitungan yang deterministik dan bebas-negara, maka Anda tidak (dan tidak seharusnya) mengejeknya. Jika ya, Anda berisiko tiruan Anda berperilaku berbeda terhadap kode asli, yang mengarah ke bug dalam pengujian Anda.Mock hanya diperlukan untuk kode yang membaca / menulis ke sistem file, database, URL titik akhir dll; yang tergantung pada lingkungan tempat Anda menjalankan; atau yang sangat stateful dan non-deterministik. Jadi, jika Anda menjaga agar bagian-bagian kode menjadi minimum dan menyembunyikannya di belakang abstraksi, maka mereka mudah untuk diejek dan sisa kode Anda menghindari perlunya mengejek.
Untuk poin kode yang memiliki efek samping, ada baiknya menulis tes yang mengejek dan tes yang tidak. Yang terakhir perlu perawatan karena mereka akan rapuh dan mungkin lambat. Jadi, Anda mungkin ingin hanya menjalankannya semalaman di server CI, daripada setiap kali Anda menyimpan dan membangun kode Anda. Tes sebelumnya harus dijalankan sesering mungkin. Seperti apakah setiap tes kemudian tes unit atau integrasi menjadi akademik dan menghindari "perang api" atas apa yang merupakan dan bukan tes unit.
sumber
R.equals
.). Karena ini sebagian besar fungsi murni, mereka umumnya tidak dipermainkan dalam tes.Pertanyaan-pertanyaan ini sangat berbeda dalam kesulitannya. Mari kita ambil pertanyaan 2 dulu.
Tes unit dan tes integrasi dipisahkan dengan jelas. Tes unit menguji satu unit (metode atau kelas) dan menggunakan unit lain hanya sebanyak yang diperlukan untuk mencapai tujuan itu. Mengejek mungkin diperlukan, tetapi itu bukan titik ujian. Tes integrasi menguji interaksi antara unit aktual yang berbeda . Perbedaan ini adalah seluruh alasan mengapa kita membutuhkan pengujian unit dan integrasi - jika seseorang melakukan pekerjaan yang cukup baik, kita tidak akan melakukannya, tetapi ternyata biasanya lebih efisien menggunakan dua alat khusus daripada satu alat umum .
Sekarang untuk pertanyaan penting: Bagaimana seharusnya Anda menguji unit? Seperti dikatakan di atas, unit test harus membangun struktur tambahan hanya sejauh yang diperlukan . Seringkali lebih mudah untuk menggunakan database mock dari database yang nyata atau bahkan setiap database yang nyata. Namun, mengejek itu sendiri tidak memiliki nilai. Jika sering terjadi itu sebenarnya lebih mudah untuk menggunakan komponen aktual dari lapisan lain sebagai input untuk uji unit tingkat menengah. Jika demikian, jangan ragu untuk menggunakannya.
Banyak praktisi takut bahwa jika tes unit B menggunakan kembali kelas yang sudah diuji oleh tes unit A, maka cacat pada unit A menyebabkan kegagalan tes di banyak tempat. Saya menganggap ini bukan masalah: test suite harus berhasil 100% untuk memberi Anda kepastian yang Anda butuhkan, jadi bukan masalah besar untuk memiliki terlalu banyak kegagalan - lagi pula, Anda memang memiliki cacat. Satu-satunya masalah kritis adalah jika cacat memicu terlalu sedikit kegagalan.
Karena itu, jangan membuat agama mengejek. Ini adalah cara, bukan tujuan, jadi jika Anda bisa menghindari upaya ekstra, Anda harus melakukannya.
sumber
The only critical problem would be if a defect triggered too few failures.
ini adalah salah satu kelemahan dari mengejek. Kita harus "memprogram" perilaku yang diharapkan jadi, kita mungkin gagal melakukannya, menyebabkan tes kita berakhir sebagai "false positive". Tetapi mengejek adalah teknik yang sangat berguna untuk mencapai determinisme (kondisi pengujian yang paling penting). Saya menggunakannya di semua proyek saya bila memungkinkan. Mereka juga menunjukkan kepada saya ketika integrasi terlalu kompleks atau ketergantungan terlalu ketat.OK, jadi untuk menjawab pertanyaan Anda secara langsung:
Seperti yang Anda katakan, Anda harus mengejek dependensi dan hanya menguji unit yang dimaksud.
Tes Integrasi adalah tes unit di mana dependensi Anda tidak diejek.
Tidak. Anda harus menyuntikkan dependensi kalkulator ke dalam kode ini dan Anda memiliki pilihan antara versi tiruan atau yang asli. Jika Anda menggunakan yang diejek itu adalah unit test, jika Anda menggunakan yang asli itu tes integrasi.
Namun, sebuah peringatan. apakah Anda benar-benar peduli apa yang menurut orang tes Anda seharusnya disebut?
Tetapi pertanyaan sebenarnya Anda tampaknya adalah ini:
Saya pikir masalahnya di sini adalah bahwa banyak orang menggunakan tiruan untuk sepenuhnya menciptakan kembali dependensi. Misalnya saya dapat mengejek kalkulator dalam contoh Anda sebagai
Saya tidak akan melakukan sesuatu seperti:
Saya berpendapat bahwa, itu akan "menguji mock saya" atau "menguji implementasi". Saya akan mengatakan " Jangan menulis Mock! * Seperti itu".
Orang lain akan tidak setuju dengan saya, kami akan memulai perang api besar di blog kami tentang Cara terbaik untuk Mock, yang benar-benar tidak masuk akal kecuali Anda memahami seluruh latar belakang dari berbagai pendekatan dan benar-benar tidak menawarkan banyak nilai. kepada seseorang yang hanya ingin menulis tes yang bagus.
sumber
MockCalc
menjadiStubCalc
, dan menyebutnya sebuah rintisan bukan tiruan. martinfowler.com/articles/…Aturan praktis saya adalah unit test yang tepat:
Jika ada kelas utilitas dari kerangka kerja yang mempersulit pengujian unit, Anda bahkan mungkin merasa berguna untuk membuat antarmuka dan kelas "wrapper" yang sangat sederhana untuk memfasilitasi mengejek dependensi tersebut. Pembungkus-pembungkus itu tidak harus diuji unit.
Saya telah menemukan perbedaan ini menjadi yang paling berguna:
Ada area abu-abu di sini. Misalnya, jika Anda dapat menjalankan aplikasi dalam wadah Docker dan menjalankan tes integrasi sebagai tahap akhir pembuatan, dan menghancurkan wadah setelahnya, apakah boleh memasukkan tes-tes tersebut sebagai "tes unit"? Jika ini perdebatan sengit Anda, Anda berada di tempat yang cukup bagus.
Tidak. Beberapa kasus uji individual akan untuk kondisi kesalahan, seperti lulus
null
sebagai parameter dan memverifikasi Anda mendapatkan pengecualian. Banyak tes seperti itu tidak akan membutuhkan cemoohan. Juga, implementasi yang tidak memiliki efek samping, misalnya pemrosesan string atau fungsi matematika, mungkin tidak memerlukan cemoohan karena Anda cukup memverifikasi output. Tetapi sebagian besar kelas bernilai, saya pikir, akan membutuhkan setidaknya satu tiruan di suatu tempat dalam kode tes. (Semakin sedikit, semakin baik.)Masalah "bau kode" yang Anda sebutkan muncul ketika Anda memiliki kelas yang terlalu rumit, yang membutuhkan daftar panjang dependensi tiruan untuk menulis tes Anda. Ini adalah petunjuk bahwa Anda perlu memperbaiki implementasi dan membagi hal-hal, sehingga setiap kelas memiliki tapak yang lebih kecil dan tanggung jawab yang lebih jelas, dan karenanya lebih mudah diuji. Ini akan meningkatkan kualitas dalam jangka panjang.
Saya tidak berpikir ini adalah harapan yang masuk akal, karena itu berfungsi melawan penggunaan kembali. Anda mungkin memiliki
private
metode, misalnya, yang dipanggil dengan beberapapublic
metode yang diterbitkan oleh antarmuka Anda. Bug yang diperkenalkan ke dalam satu metode itu kemudian dapat menyebabkan beberapa kegagalan pengujian. Ini tidak berarti Anda harus menyalin kode yang sama ke setiappublic
metode.sumber
Saya tidak begitu yakin bagaimana aturan ini bermanfaat. Jika perubahan dalam satu kelas / metode / apa pun dapat merusak perilaku yang lain dalam kode produksi, maka hal-hal tersebut, pada kenyataannya, kolaborator, dan tidak terkait. Jika tes Anda rusak dan kode produksi Anda tidak, maka tes Anda dicurigai.
Saya akan menganggap aturan ini dengan kecurigaan juga. Jika Anda benar-benar cukup baik untuk menyusun kode Anda dan menulis tes Anda sehingga satu bug menyebabkan kegagalan satu unit, maka Anda mengatakan bahwa Anda telah mengidentifikasi semua bug potensial, bahkan ketika basis kode berkembang untuk menggunakan kasing yang Anda miliki. belum diantisipasi.
Saya tidak berpikir itu perbedaan penting. Apa itu 'unit' kode?
Cobalah untuk menemukan titik masuk di mana Anda dapat menulis tes yang 'masuk akal' dalam hal masalah domain / aturan bisnis yang ditangani oleh tingkat kode tersebut. Seringkali tes ini agak 'fungsional' - dimasukkan ke dalam input, dan uji bahwa output seperti yang diharapkan. Jika tes menyatakan perilaku sistem yang diinginkan, maka tes tersebut sering tetap stabil bahkan ketika kode produksi berevolusi dan direaktor ulang.
Jangan membaca terlalu banyak ke dalam kata 'unit', dan condong ke arah menggunakan kelas produksi nyata Anda dalam pengujian, tanpa terlalu khawatir jika Anda melibatkan lebih dari satu dari mereka dalam tes. Jika salah satu dari mereka sulit digunakan (karena butuh banyak inisialisasi, atau perlu menekan database nyata / server email dll), maka biarkan pikiran Anda beralih ke mengejek / berpura-pura.
sumber
Person:tellStory()
metode menggabungkan detail seseorang ke dalam string kemudian mengembalikannya, maka "cerita" mungkin satu unit. Jika saya membuat metode pembantu pribadi yang menghilangkan beberapa kode, maka saya tidak percaya saya telah memperkenalkan unit baru - saya tidak perlu mengujinya secara terpisah.Pertama, beberapa definisi:
Sebuah unit menguji unit secara terpisah dari unit lain, tetapi apa artinya itu tidak secara konkret ditentukan oleh sumber otoritatif, jadi mari kita mendefinisikannya sedikit lebih baik: Jika batas I / O dilintasi (apakah I / O itu jaringan, disk, layar, atau input UI), ada tempat semi-objektif yang dapat kita gambar garis. Jika kode tergantung pada I / O, itu melintasi batas unit, dan karena itu perlu mengejek unit yang bertanggung jawab untuk I / O itu.
Di bawah definisi itu, saya tidak melihat alasan kuat untuk mengejek hal-hal seperti fungsi murni, yang berarti bahwa pengujian unit cocok untuk fungsi murni, atau fungsi tanpa efek samping.
Jika Anda ingin unit unit tes dengan efek, unit yang bertanggung jawab atas efek harus diejek, tetapi mungkin Anda harus mempertimbangkan tes integrasi. Jadi, jawaban singkatnya adalah: "jika Anda perlu mengejek, tanyakan pada diri sendiri apakah yang benar-benar Anda butuhkan adalah tes integrasi." Tapi ada jawaban yang lebih baik, lebih lama di sini, dan lubang kelinci jauh lebih dalam. Mengolok-olok mungkin bau kode favorit saya karena ada begitu banyak yang bisa dipelajari dari mereka.
Kode Berbau
Untuk ini, kami akan beralih ke Wikipedia:
Itu berlanjut nanti ...
Dengan kata lain, tidak semua kode bau. Sebaliknya, mereka adalah indikasi umum bahwa sesuatu mungkin tidak diungkapkan dalam bentuk optimal, dan bau dapat mengindikasikan peluang untuk memperbaiki kode yang dimaksud.
Dalam kasus ejekan, bau menandakan bahwa unit yang tampaknya membutuhkan ejekan tergantung pada unit yang akan diejek. Ini mungkin merupakan indikasi bahwa kita belum menguraikan masalah menjadi potongan-potongan yang dapat dipecahkan secara atom, dan itu bisa menunjukkan cacat desain pada perangkat lunak.
Inti dari semua pengembangan perangkat lunak adalah proses memecah masalah besar menjadi lebih kecil, potongan-potongan independen (dekomposisi) dan menyusun solusi bersama untuk membentuk aplikasi yang memecahkan masalah besar (komposisi).
Mengejek diperlukan ketika unit yang digunakan untuk memecah masalah besar menjadi bagian-bagian yang lebih kecil bergantung satu sama lain. Dengan kata lain, mengejek diperlukan ketika unit atom yang seharusnya kita komposisi tidak benar-benar atomik, dan strategi dekomposisi kita telah gagal untuk menguraikan masalah yang lebih besar menjadi lebih kecil, masalah independen yang harus diselesaikan.
Apa yang membuat mengolok-olok bau kode bukanlah bahwa ada sesuatu yang salah dengan mengolok-olok - kadang-kadang itu sangat berguna. Apa yang membuatnya menjadi bau kode adalah bahwa itu bisa menunjukkan sumber kopling yang bermasalah dalam aplikasi Anda. Kadang-kadang menghilangkan sumber sambungan jauh lebih produktif daripada menulis tiruan.
Ada banyak jenis kopling, dan beberapa lebih baik daripada yang lain. Memahami bahwa mengejek adalah bau kode dapat mengajarkan Anda untuk mengidentifikasi dan menghindari jenis terburuk di awal siklus hidup desain aplikasi, sebelum bau berkembang menjadi sesuatu yang lebih buruk.
sumber
Mengejek seharusnya hanya digunakan sebagai upaya terakhir, bahkan dalam tes unit.
Metode bukanlah sebuah unit, dan bahkan sebuah kelas bukanlah sebuah unit. Unit adalah setiap pemisahan logis dari kode yang masuk akal, terlepas dari apa yang Anda sebut. Elemen penting dari memiliki kode yang teruji dengan baik adalah dapat secara bebas melakukan refactor, dan bagian dari kemampuan untuk secara bebas melakukan refactor berarti Anda tidak perlu mengubah tes Anda untuk melakukannya. Semakin banyak Anda mengejek, semakin banyak Anda harus mengubah tes saat refactor. Jika Anda mempertimbangkan metode unit, maka Anda harus mengubah tes Anda setiap kali Anda refactor. Dan jika Anda menganggap kelas sebagai unit, maka Anda harus mengubah tes Anda setiap kali Anda ingin membagi kelas menjadi beberapa kelas. Ketika Anda harus memperbaiki tes Anda untuk memperbaiki kode Anda, itu membuat orang memilih untuk tidak memperbaiki kode mereka, yang merupakan hal terburuk yang dapat terjadi pada suatu proyek. Sangat penting bahwa Anda dapat membagi kelas menjadi beberapa kelas tanpa harus memperbaiki tes Anda, atau Anda akan berakhir dengan kelas spaghetti 500 baris yang terlalu besar. Jika Anda memperlakukan metode atau kelas sebagai unit Anda dengan pengujian unit, Anda mungkin tidak melakukan Pemrograman Berorientasi Objek tetapi semacam pemrograman fungsional mutan dengan objek.
Mengisolasi kode Anda untuk unit test tidak berarti Anda mengejek semua yang ada di luarnya. Jika ya, Anda harus mengejek kelas Matematika bahasa Anda, dan sama sekali tidak ada yang berpikir itu ide yang bagus. Ketergantungan internal tidak boleh diperlakukan secara berbeda dari dependensi eksternal. Anda percaya bahwa mereka diuji dengan baik dan bekerja seperti yang seharusnya. Satu-satunya perbedaan nyata adalah bahwa jika dependensi internal Anda melanggar modul Anda, Anda dapat menghentikan apa yang Anda lakukan untuk memperbaikinya daripada harus mengirim masalah pada GitHub dan menggali basis kode yang tidak Anda mengerti untuk memperbaikinya atau berharap yang terbaik.
Mengisolasi kode Anda hanya berarti Anda memperlakukan dependensi internal Anda seperti kotak hitam dan tidak menguji hal-hal yang terjadi di dalamnya. Jika Anda memiliki Modul B yang menerima input 1, 2, atau 3, dan Anda memiliki Modul A, yang menyebutnya, Anda tidak memiliki tes untuk Modul A melakukan setiap opsi tersebut, Anda hanya memilih satu dan menggunakannya. Ini berarti bahwa tes Anda untuk Modul A harus menguji berbagai cara Anda memperlakukan tanggapan dari Modul B, bukan hal-hal yang Anda sampaikan.
Jadi, jika controller Anda meneruskan objek kompleks ke dependensi, dan dependensi melakukan beberapa hal yang mungkin, mungkin menyimpannya ke database dan mungkin mengembalikan berbagai kesalahan, tetapi semua controller Anda sebenarnya hanya memeriksa untuk melihat apakah itu mengembalikan kesalahan atau tidak dan meneruskan informasi itu, maka semua yang Anda uji di controller Anda adalah satu tes untuk apakah itu mengembalikan kesalahan dan meneruskannya dan satu tes untuk jika itu tidak mengembalikan kesalahan. Anda tidak menguji apakah ada sesuatu yang disimpan dalam database atau jenis kesalahan apa kesalahan itu, karena itu akan menjadi tes integrasi. Anda tidak perlu mengejek ketergantungan untuk melakukan ini. Anda telah mengisolasi kodenya.
sumber