Anda memiliki kelas X dan Anda menulis beberapa unit test yang memverifikasi perilaku X1. Ada juga kelas A yang menganggap X sebagai ketergantungan.
Ketika Anda menulis tes unit untuk A, Anda mengejek X. Dengan kata lain, saat pengujian unit A, Anda mengatur (mendalilkan) perilaku dari tiruan X menjadi X1. Waktu berlalu, orang menggunakan sistem Anda, perlu berubah, X berevolusi: Anda memodifikasi X untuk menunjukkan perilaku X2. Jelas, tes unit untuk X akan gagal dan Anda harus menyesuaikannya.
Tapi bagaimana dengan A? Tes unit untuk A tidak akan gagal ketika perilaku X diubah (karena mengejek X). Bagaimana mendeteksi bahwa hasil A akan berbeda ketika dijalankan dengan X "asli" (dimodifikasi)?
Saya mengharapkan jawaban di sepanjang baris: "Itu bukan tujuan pengujian unit", tetapi nilai apa yang dimiliki unit testing? Apakah itu benar-benar hanya memberitahu Anda bahwa ketika semua tes lulus, Anda belum memperkenalkan perubahan yang melanggar? Dan ketika beberapa perilaku kelas berubah (secara sukarela atau tidak), bagaimana Anda dapat mendeteksi (lebih disukai dengan cara otomatis) semua konsekuensinya? Bukankah seharusnya kita lebih fokus pada pengujian integrasi?
sumber
X1
Anda mengatakan bahwaX
mengimplementasikan antarmukaX1
. Jika Anda mengubah antarmukaX1
keX2
tiruan yang Anda gunakan dalam tes lain tidak boleh dikompilasi lagi, maka Anda terpaksa memperbaiki tes itu juga. Perubahan perilaku kelas seharusnya tidak menjadi masalah. Bahkan, kelas AndaA
seharusnya tidak bergantung pada detail implementasi (yang akan Anda ubah dalam kasus itu). Jadi tes unitA
masih benar, dan mereka memberi tahu Anda bahwaA
berfungsi memberikan implementasi antarmuka yang ideal.Jawaban:
Apakah kamu? Saya tidak, kecuali saya benar-benar harus. Saya harus jika:
X
lambat, atauX
memiliki efek sampingJika tak satu pun dari ini berlaku, maka unit test saya
A
akan mengujiX
juga. Melakukan hal lain akan mengambil tes isolasi ke ekstrim yang tidak masuk akal.Jika Anda memiliki bagian dari kode Anda menggunakan ejekan bagian lain dari kode Anda, maka saya setuju: apa gunanya tes unit tersebut? Jadi jangan lakukan ini. Biarkan tes-tes itu menggunakan dependensi nyata karena mereka membentuk tes yang jauh lebih berharga seperti itu.
Dan jika beberapa orang marah dengan Anda menyebut tes ini, "tes unit", maka sebut saja "tes otomatis" dan mulai menulis tes otomatis yang bagus.
sumber
Anda membutuhkan keduanya. Tes unit untuk memverifikasi perilaku masing-masing unit Anda, dan beberapa tes integrasi untuk memastikan mereka terhubung dengan benar. Masalah dengan hanya mengandalkan pengujian integrasi adalah ledakan kombinatorial yang dihasilkan dari interaksi antara semua unit Anda.
Katakanlah Anda memiliki kelas A, yang membutuhkan 10 tes unit untuk sepenuhnya mencakup semua jalur. Kemudian Anda memiliki kelas B lainnya, yang juga membutuhkan 10 unit tes untuk mencakup semua jalur yang dapat dilalui kode. Sekarang katakanlah dalam aplikasi Anda, Anda perlu memberi makan output A ke B. Sekarang kode Anda dapat mengambil 100 jalur berbeda dari input A ke output B.
Dengan tes unit, Anda hanya perlu 20 tes unit + 1 tes integrasi untuk sepenuhnya mencakup semua kasus.
Dengan tes integrasi, Anda akan membutuhkan 100 tes untuk mencakup semua jalur kode.
Berikut adalah video yang sangat bagus tentang kelemahan mengandalkan tes integrasi hanya Uji Terpadu JB Rainsberger Are A Scam HD
sumber
Woah, tunggu sebentar. Implikasi dari tes untuk X gagal terlalu penting untuk ditutup-tutupi seperti itu.
Jika mengubah implementasi X dari X1 ke X2 mematahkan tes unit untuk X, itu menunjukkan bahwa Anda telah membuat perubahan mundur yang tidak kompatibel ke kontrak X.
X2 bukan X, dalam arti Liskov , jadi Anda harus memikirkan cara lain untuk memenuhi kebutuhan pemegang saham Anda (seperti memperkenalkan spesifikasi Y yang baru, yang diterapkan oleh X2).
Untuk wawasan lebih dalam, lihat Pieter Hinjens: Akhir Versi Perangkat Lunak , atau Rich Hickey Simple Made Easy .
Dari perspektif A, ada prasyarat bahwa kolaborator menghormati kontrak X. Dan pengamatan Anda secara efektif bahwa tes terisolasi untuk A tidak memberi Anda jaminan bahwa A mengakui kolaborator yang melanggar kontrak X.
Ulasan Tes Terpadu adalah Penipuan ; pada level tinggi, Anda diharapkan memiliki tes terisolasi sebanyak yang Anda perlukan untuk memastikan bahwa X2 mengimplementasikan kontrak X dengan benar, dan sebanyak mungkin tes terisolasi yang Anda perlukan untuk memastikan bahwa A melakukan hal yang benar dengan memberikan tanggapan yang menarik dari X, dan sejumlah kecil tes terintegrasi untuk memastikan bahwa X2 dan A menyepakati apa yang X maksudkan.
Terkadang Anda akan melihat perbedaan ini dinyatakan sebagai tes soliter vs
sociable
tes; lihat Jay Fields Bekerja Efektif dengan Unit Tes .Sekali lagi, lihat tes terintegrasi adalah penipuan - Rainsberger menjelaskan secara terperinci lingkaran umpan balik positif yang umum (dalam pengalamannya) untuk proyek yang mengandalkan tes terintegrasi (note spelling). Singkatnya, tanpa tes terisolasi / soliter memberikan tekanan pada desain , kualitasnya menurun, menyebabkan lebih banyak kesalahan dan tes yang lebih terintegrasi ....
Anda juga akan memerlukan (beberapa) tes integrasi. Selain kompleksitas yang diperkenalkan oleh banyak modul, mengeksekusi tes ini cenderung memiliki hambatan lebih dari tes terisolasi; lebih efisien untuk melakukan iterasi pada cek sangat cepat ketika pekerjaan sedang berlangsung, menyimpan cek tambahan ketika Anda berpikir Anda "selesai".
sumber
Mari saya mulai dengan mengatakan bahwa premis inti dari pertanyaan itu cacat.
Anda tidak pernah menguji (atau mengejek) implementasi, Anda menguji (dan mengejek) antarmuka .
Jika saya memiliki kelas X nyata yang mengimplementasikan antarmuka X1, saya dapat menulis tiruan XM yang juga sesuai dengan X1. Maka kelas A saya harus menggunakan sesuatu yang mengimplementasikan X1, yang bisa berupa kelas X atau mock XM.
Sekarang, misalkan kita mengubah X untuk mengimplementasikan antarmuka baru X2. Yah, jelas kode saya tidak lagi dikompilasi. A membutuhkan sesuatu yang mengimplementasikan X1, dan yang sudah tidak ada lagi. Masalah telah diidentifikasi dan dapat diperbaiki.
Misalkan alih-alih mengganti X1, kami hanya memodifikasinya. Sekarang kelas A sudah siap. Namun, mock XM tidak lagi mengimplementasikan antarmuka X1. Masalah telah diidentifikasi dan dapat diperbaiki.
Seluruh dasar untuk pengujian dan mengejek unit adalah Anda menulis kode yang menggunakan antarmuka. Pengguna antarmuka tidak peduli bagaimana kode diterapkan, hanya saja kontrak yang sama dipatuhi (input / output).
Ini rusak ketika metode Anda memiliki efek samping, tapi saya pikir itu dapat dengan aman dikecualikan sebagai "tidak dapat diuji atau diejek unit".
sumber
Secara bergiliran menjawab pertanyaan Anda:
Mereka murah untuk menulis dan menjalankan dan Anda mendapatkan umpan balik awal. Jika Anda melanggar X, Anda akan segera mengetahui apakah Anda memiliki tes yang bagus. Jangan pernah mempertimbangkan untuk menulis tes integrasi kecuali Anda telah menguji semua layer Anda (ya, bahkan pada basis data).
Memiliki tes yang lulus sebenarnya bisa memberi tahu Anda sangat sedikit. Anda mungkin tidak memiliki cukup tes tertulis. Anda mungkin belum menguji cukup skenario. Cakupan kode dapat membantu di sini tetapi itu bukan peluru perak. Anda mungkin memiliki tes yang selalu lulus. Oleh karena itu merah menjadi langkah pertama yang sering diabaikan dari merah, hijau, refactor.
Lebih banyak pengujian - meskipun alat menjadi lebih baik dan lebih baik. TETAPI Anda harus mendefinisikan perilaku kelas dalam suatu antarmuka (lihat di bawah). NB akan selalu ada tempat untuk pengujian manual di atas piramida pengujian.
Semakin banyak tes integrasi juga bukan jawabannya, mereka mahal untuk ditulis, dijalankan, dan dipelihara. Bergantung pada pengaturan build Anda, build manager Anda mungkin mengecualikan mereka sehingga membuatnya bergantung pada pengembang yang mengingat (tidak pernah menjadi hal yang baik!).
Saya telah melihat pengembang menghabiskan waktu berjam-jam untuk mencoba memperbaiki tes integrasi yang mereka temukan dalam lima menit jika mereka memiliki tes unit yang baik. Jika gagal, coba jalankan perangkat lunaknya - hanya itu yang akan dipedulikan pengguna akhir Anda. Tidak ada gunanya memiliki jutaan unit tes yang lulus jika seluruh kartu jatuh ketika pengguna menjalankan seluruh rangkaian.
Jika Anda ingin memastikan kelas A mengkonsumsi kelas X dengan cara yang sama, Anda harus menggunakan antarmuka daripada konkret. Kemudian perubahan yang melanggar lebih mungkin diambil pada waktu kompilasi.
sumber
Itu benar.
Tes unit ada untuk menguji fungsionalitas unit yang terisolasi, pemeriksaan sekilas yang berfungsi sebagaimana dimaksud dan tidak mengandung kesalahan bodoh.
Tes unit tidak ada untuk menguji apakah seluruh aplikasi berfungsi.
Apa yang banyak orang lupakan adalah bahwa unit test hanyalah cara tercepat dan paling kotor untuk memvalidasi kode Anda. Setelah Anda tahu bahwa rutinitas kecil Anda berfungsi, Anda harus menjalankan tes Integrasi juga. Pengujian unit dengan sendirinya hanya sedikit lebih baik daripada tidak ada pengujian.
Alasan kami memiliki unit test sama sekali adalah bahwa mereka seharusnya murah. Cepat untuk membuat dan menjalankan dan memelihara. Setelah Anda mulai mengubahnya menjadi tes integrasi minimal, Anda menjadi dunia yang penuh kesakitan. Anda mungkin harus melakukan tes Integrasi penuh dan mengabaikan pengujian unit sama sekali jika Anda akan melakukannya.
sekarang, beberapa orang berpikir bahwa sebuah unit bukan hanya fungsi dalam kelas, tetapi seluruh kelas itu sendiri (termasuk saya sendiri). Namun, yang dilakukan adalah menambah ukuran unit sehingga Anda mungkin memerlukan lebih sedikit pengujian integrasi, tetapi Anda masih membutuhkannya. Masih tidak mungkin memverifikasi program Anda melakukan apa yang seharusnya dilakukan tanpa paket uji integrasi lengkap.
dan kemudian, Anda masih harus menjalankan pengujian integrasi penuh pada sistem langsung (atau semi-live) untuk memastikan bahwa itu bekerja dengan kondisi yang digunakan pelanggan.
sumber
Tes unit tidak membuktikan kebenaran apa pun. Ini berlaku untuk semua tes. Biasanya pengujian unit dikombinasikan dengan desain berbasis kontrak (desain dengan kontrak adalah cara lain untuk mengatakannya) dan mungkin bukti kebenaran otomatis, jika kebenaran perlu diverifikasi secara teratur.
Jika Anda memiliki kontrak nyata, yang terdiri dari invarian kelas, prasyarat, dan kondisi pos, dimungkinkan untuk membuktikan kebenaran secara hierarkis, dengan mendasarkan kebenaran komponen tingkat yang lebih tinggi pada kontrak komponen tingkat yang lebih rendah. Ini konsep dasar di balik desain berdasarkan kontrak.
sumber
fac(5) == 120
, Anda belum membuktikan bahwafac()
memang mengembalikan faktorial dari argumennya. Anda hanya membuktikan yangfac()
mengembalikan faktorial lima ketika Anda masuk5
. Dan bahkan itu tidak pasti, karenafac()
dapat kembali42
pada hari Senin pertama setelah gerhana total di Timbuktu ... Masalahnya di sini adalah, bahwa Anda tidak dapat membuktikan kepatuhan dengan memeriksa masing-masing input pengujian, Anda perlu memeriksa semua input yang mungkin, dan juga buktikan bahwa Anda belum melupakan (seperti membaca jam sistem).Saya menemukan tes sangat diejek jarang berguna. Sebagian besar waktu, saya akhirnya menerapkan kembali perilaku yang sudah dimiliki kelas asli, yang benar-benar mengalahkan tujuan mengejek.
Saya strategi yang lebih baik adalah memiliki pemisahan keprihatinan yang baik (mis. Anda dapat menguji Bagian A dari aplikasi Anda tanpa membawa bagian B hingga Z). Arsitektur yang begitu bagus sangat membantu untuk menulis tes yang bagus.
Juga, saya lebih dari mau menerima efek samping selama saya bisa mengembalikannya, misalnya jika metode saya memodifikasi data dalam db, biarkan saja! Selama saya bisa mengembalikan db ke kondisi sebelumnya, apa salahnya? Juga, ada manfaat yang bisa diperiksa oleh pengujian saya jika data tampak seperti yang diharapkan. DB dalam Memori atau versi uji spesifik dbs sangat membantu di sini (mis. Versi RavenDBs dalam memori).
Akhirnya, saya suka melakukan ejekan pada batas-batas layanan, mis. Jangan membuat panggilan http ke layanan b, tapi mari kita sela dan perkenalkan yang tepat
sumber
Saya berharap orang-orang di kedua kubu akan memahami bahwa pengujian kelas dan pengujian perilaku tidak ortogonal.
Pengujian kelas dan pengujian unit digunakan secara bergantian dan mungkin tidak seharusnya demikian. Beberapa unit test kebetulan diimplementasikan di kelas. Itu semuanya. Pengujian unit telah terjadi selama beberapa dekade dalam bahasa tanpa kelas.
Adapun perilaku pengujian, sangat mungkin untuk melakukan ini dalam pengujian kelas menggunakan konstruk GWT.
Selain itu, apakah tes otomatis Anda berjalan di sepanjang kelas atau garis perilaku lebih tergantung pada prioritas Anda. Beberapa perlu prototipe dengan cepat dan mengeluarkan sesuatu sementara yang lain akan memiliki batasan cakupan karena gaya rumah. Banyak alasan. Keduanya merupakan pendekatan yang benar-benar valid. Anda membayar uang Anda, Anda mengambil pilihan Anda.
Jadi, apa yang harus dilakukan ketika kode rusak. Jika telah dikodekan ke antarmuka, maka konkret hanya perlu diubah (bersama dengan tes apa pun).
Namun, memperkenalkan perilaku baru tidak perlu membahayakan sistem sama sekali. Linux dkk penuh dengan fitur-fitur usang. Dan hal-hal seperti konstruktor (dan metode) dapat dengan senang hati kelebihan beban tanpa memaksa semua kode panggilan untuk berubah.
Di mana pengujian kelas menang adalah di mana Anda perlu membuat perubahan ke kelas yang belum diselami (karena kendala waktu, kompleksitas, atau apa pun). Jauh lebih mudah untuk memulai dengan kelas jika memiliki tes komprehensif.
sumber
Kecuali jika antarmuka untuk X berubah, Anda tidak perlu mengubah unit test untuk A karena tidak ada yang terkait dengan A telah berubah. Sepertinya Anda benar-benar menulis unit test X dan A bersama-sama, tetapi menyebutnya unit test A:
Idealnya, tiruan X harus mensimulasikan semua perilaku X yang mungkin, bukan hanya perilaku yang Anda harapkan dari X. Jadi, apa pun yang sebenarnya Anda implementasikan dalam X, A harus sudah mampu menanganinya. Jadi tidak ada perubahan ke X, selain mengubah antarmuka itu sendiri, akan berpengaruh pada unit test untuk A.
Sebagai contoh: Misalkan A adalah algoritma pengurutan, dan A menyediakan data untuk diurutkan. Mock of X harus memberikan nilai balik nol, daftar data kosong, elemen tunggal, beberapa elemen yang sudah diurutkan, beberapa elemen belum diurutkan, beberapa elemen diurutkan mundur, daftar dengan elemen yang sama diulang, nilai-nilai null saling terkait, sebuah ridiculously sejumlah besar elemen, dan juga harus membuang pengecualian.
Jadi mungkin X awalnya mengembalikan data yang diurutkan pada hari Senin dan daftar kosong pada hari Selasa. Tapi sekarang ketika X mengembalikan data yang tidak disortir pada hari Senin dan melempar pengecualian pada hari Selasa, A tidak peduli - skenario itu sudah tercakup dalam uji unit A.
sumber
Anda harus melihat berbagai tes.
Unit test sendiri hanya akan menguji X. Mereka ada di sana untuk mencegah Anda mengubah perilaku X tetapi tidak mengamankan keseluruhan sistem. Mereka memastikan Anda dapat memperbaiki kelas Anda tanpa memperkenalkan perubahan perilaku. Dan jika Anda memecahkan X, Anda memecahkannya ...
Seharusnya A benar-benar mengejek X untuk tes unitnya dan tes dengan Mock harus terus berlalu bahkan setelah Anda mengubahnya.
Tetapi ada lebih dari satu level pengujian! Ada juga tes integrasi. Tes-tes ini ada untuk memvalidasi interaksi antara kelas-kelas. Tes ini biasanya memiliki harga yang lebih tinggi karena mereka tidak menggunakan ejekan untuk semuanya. Sebagai contoh, tes integrasi mungkin benar-benar menulis catatan ke dalam database, dan tes unit seharusnya tidak memiliki dependensi eksternal.
Juga jika X perlu memiliki perilaku baru, akan lebih baik untuk menyediakan metode baru yang akan memberikan hasil yang diinginkan
sumber