Misalkan saya memiliki kelas (maafkan contoh yang dibuat-buat dan desain buruknya):
class MyProfit
{
public decimal GetNewYorkRevenue();
public decimal GetNewYorkExpenses();
public decimal GetNewYorkProfit();
public decimal GetMiamiRevenue();
public decimal GetMiamiExpenses();
public decimal GetMiamiProfit();
public bool BothCitiesProfitable();
}
(Perhatikan metode GetxxxRevenue () dan GetxxxExpenses () memiliki dependensi yang terhapus)
Sekarang saya menguji unit BothCitiesProfitable () yang tergantung pada GetNewYorkProfit () dan GetMiamiProfit (). Apakah saya tetap bisa mematikan GetNewYorkProfit () dan GetMiamiProfit ()?
Sepertinya jika saya tidak melakukannya, saya secara bersamaan menguji GetNewYorkProfit () dan GetMiamiProfit () bersama dengan BothCitiesProfitable (). Saya harus memastikan bahwa saya mensetup stubbing untuk GetxxxRevenue () dan GetxxxExpenses () sehingga metode GetxxxProfit () mengembalikan nilai yang benar.
Sejauh ini saya hanya melihat contoh ketergantungan stubbing pada kelas eksternal bukan metode internal.
Dan jika tidak apa-apa, apakah ada pola tertentu yang harus saya gunakan untuk melakukan ini?
MEMPERBARUI
Saya khawatir bahwa kita mungkin kehilangan masalah inti dan itu mungkin kesalahan dari contoh buruk saya. Pertanyaan mendasarnya adalah: jika suatu metode di kelas memiliki ketergantungan pada metode lain yang terbuka untuk umum di kelas yang sama, apakah boleh (atau bahkan disarankan) untuk mematikan metode yang lain?
Mungkin saya kehilangan sesuatu, tetapi saya tidak yakin berpisah dengan kelas selalu masuk akal. Mungkin contoh lain yang lebih baik adalah:
class Person
{
public string FirstName()
public string LastName()
public string FullName()
}
di mana nama lengkap didefinisikan sebagai:
public string FullName()
{
return FirstName() + " " + LastName();
}
Apakah saya boleh membubarkan FirstName () dan LastName () saat menguji FullName ()?
sumber
Jawaban:
Anda harus memecah kelas dalam pertanyaan.
Setiap kelas harus melakukan beberapa tugas sederhana. Jika tugas Anda terlalu rumit untuk diuji, maka tugas yang dilakukan kelas terlalu besar.
Mengabaikan kesalahan desain ini:
MEMPERBARUI
Masalah dengan metode stubbing di kelas adalah bahwa Anda melanggar enkapsulasi. Tes Anda harus memeriksa untuk melihat apakah perilaku eksternal objek cocok dengan spesifikasi. Apa pun yang terjadi di dalam objek bukan urusannya.
Fakta bahwa FullName menggunakan FirstName dan LastName adalah detail implementasi. Tidak ada yang di luar kelas yang seharusnya peduli bahwa itu benar. Dengan mengejek metode publik untuk menguji objek, Anda membuat asumsi tentang objek yang diimplementasikan.
Di beberapa titik di masa depan, asumsi itu mungkin tidak lagi benar. Mungkin semua logika nama akan dipindahkan ke objek Nama yang orang hanya memanggil. Mungkin FullName akan langsung mengakses variabel anggota first_name dan last_name daripada memanggil FirstName dan LastName.
Pertanyaan kedua adalah mengapa Anda merasa perlu melakukannya. Setelah semua kelas orang Anda dapat diuji sesuatu seperti:
Anda seharusnya tidak merasakan kebutuhan rintisan apa pun untuk contoh ini. Jika Anda melakukannya maka Anda bahagia dan baik-baik saja ... hentikan! Tidak ada gunanya mengejek metode di sana karena Anda punya kendali atas apa yang ada di dalamnya.
Satu-satunya kasus di mana tampaknya masuk akal untuk metode yang digunakan FullName untuk diejek adalah jika entah bagaimana FirstName () dan LastName () adalah operasi non-sepele. Mungkin Anda sedang menulis salah satu generator nama acak itu, atau FirstName dan LastName meminta database untuk menjawab, atau sesuatu. Tetapi jika itu yang terjadi itu menunjukkan bahwa objek melakukan sesuatu yang bukan milik kelas Person.
Dengan kata lain, mengejek metode adalah mengambil objek dan memecah menjadi dua bagian. Satu bagian sedang diejek sementara bagian lainnya sedang diuji. Apa yang Anda lakukan pada dasarnya adalah pemecahan ad-hoc objek. Jika itu masalahnya, potong saja objeknya.
Jika kelas Anda sederhana, Anda tidak perlu merasa perlu untuk mengejeknya selama ujian. Jika kelas Anda cukup kompleks sehingga Anda merasa perlu mengejek, maka Anda harus memecah kelas menjadi potongan-potongan yang lebih sederhana.
PEMBARUAN LAGI
Cara saya melihatnya, sebuah objek memiliki perilaku eksternal dan internal. Perilaku eksternal termasuk mengembalikan nilai panggilan ke objek lain dll. Jelas, apa pun dalam kategori itu harus diuji. (Kalau tidak, apa yang akan Anda uji?) Tetapi perilaku internal seharusnya tidak benar-benar diuji.
Sekarang perilaku internal diuji, karena itulah yang menghasilkan perilaku eksternal. Tetapi saya tidak menulis tes langsung pada perilaku internal, hanya secara tidak langsung melalui perilaku eksternal.
Jika saya ingin menguji sesuatu, saya pikir itu harus dipindahkan sehingga menjadi perilaku eksternal. Itu sebabnya saya pikir jika Anda ingin mengejek sesuatu, Anda harus membagi objek sehingga hal yang ingin Anda tiru sekarang dalam perilaku eksternal objek yang dimaksud.
Tapi, apa bedanya? Jika FirstName () dan LastName () adalah anggota dari objek lain apakah itu benar-benar mengubah masalah FullName ()? Jika kita memutuskan bahwa perlu mengejek FirstName dan LastName apakah sebenarnya membantu mereka untuk berada di objek lain?
Saya pikir jika Anda menggunakan pendekatan mengejek Anda, maka Anda membuat jahitan di objek. Anda memiliki fungsi seperti FirstName () dan LastName () yang langsung berkomunikasi dengan sumber data eksternal. Anda juga memiliki FullName () yang tidak. Tapi karena mereka semua berada di kelas yang sama sehingga tidak terlihat. Beberapa bagian tidak seharusnya langsung mengakses sumber data dan lainnya. Kode Anda akan lebih jelas jika hanya membagi dua kelompok itu.
EDIT
Mari kita mundur selangkah dan bertanya: mengapa kita mengejek benda ketika kita menguji?
Sekarang, saya pikir alasan 1-4 tidak berlaku untuk skenario ini. Mengejek sumber eksternal ketika menguji nama lengkap menangani semua alasan tersebut untuk mengejek. Satu-satunya bagian yang tidak ditangani adalah kesederhanaan, tetapi tampaknya objeknya cukup sederhana yang tidak menjadi perhatian.
Saya pikir kekhawatiran Anda adalah alasan nomor 5. Kekhawatirannya adalah bahwa pada suatu saat di masa depan mengubah implementasi FirstName dan LastName akan memecahkan ujian. Di masa depan FirstName dan LastName dapat memperoleh nama-nama dari lokasi atau sumber yang berbeda. Tapi FullName mungkin akan selalu begitu
FirstName() + " " + LastName()
. Itu sebabnya Anda ingin menguji FullName dengan mengejek FirstName dan LastName.Apa yang Anda miliki, adalah beberapa bagian dari objek orang yang lebih mungkin berubah daripada yang lain. Sisa objek menggunakan subset ini. Subset tersebut saat ini mengambil datanya menggunakan satu sumber, tetapi dapat mengambil data itu dengan cara yang sama sekali berbeda di kemudian hari. Tapi bagiku itu terdengar seperti subset yang merupakan objek berbeda yang berusaha keluar.
Tampak bagi saya bahwa jika Anda mengejek metode objek Anda membagi objek. Tetapi Anda melakukannya secara ad-hoc. Kode Anda tidak menjelaskan bahwa ada dua bagian berbeda di dalam objek Person Anda. Jadi cukup bagi objek itu dalam kode Anda yang sebenarnya, sehingga jelas dari membaca kode Anda apa yang terjadi. Pilih pemisahan sebenarnya dari objek yang masuk akal dan jangan mencoba untuk membagi objek secara berbeda untuk setiap tes.
Saya menduga Anda mungkin keberatan untuk memisahkan objek Anda, tetapi mengapa?
EDIT
Saya salah.
Anda harus membagi objek daripada memperkenalkan pemisahan ad-hoc dengan mengejek metode individual. Namun, saya terlalu fokus pada satu metode pemisahan objek. Namun, OO menyediakan beberapa metode pemisahan objek.
Apa yang saya usulkan:
Mungkin itu yang Anda lakukan selama ini. Tetapi saya tidak berpikir metode ini akan memiliki masalah yang saya lihat dengan metode mengejek karena kami telah dengan jelas menggambarkan sisi mana setiap metode aktif. Dan dengan menggunakan warisan, kita menghindari kecanggungan yang akan muncul jika kita menggunakan objek pembungkus tambahan.
Ini memang mengenalkan beberapa kompleksitas, dan hanya untuk beberapa fungsi utilitas saya mungkin baru saja mengujinya dengan mengejek sumber pihak ke-3 yang mendasarinya. Tentu, mereka berada dalam bahaya yang semakin besar untuk dipatahkan tetapi itu tidak layak disusun ulang. Jika Anda punya objek yang cukup kompleks sehingga Anda perlu membaginya, maka saya pikir sesuatu seperti ini adalah ide yang bagus.
sumber
Sementara saya setuju dengan jawaban Winston Ewert, kadang-kadang tidak mungkin untuk memecah kelas, apakah karena kendala waktu, API sudah digunakan di tempat lain, atau apa pun.
Dalam kasus seperti itu, alih-alih metode mengejek, saya akan menulis tes saya untuk menutupi kelas secara bertahap. Uji
getExpenses()
dangetRevenue()
metode, lalu ujigetProfit()
metode, lalu uji metode bersama. Memang benar bahwa Anda akan memiliki lebih dari satu tes yang mencakup metode tertentu, tetapi karena Anda menulis tes yang lulus untuk mencakup metode secara individual, Anda dapat yakin bahwa output Anda dapat diandalkan diberikan input yang diuji.sumber
Untuk menyederhanakan contoh Anda lebih lanjut, katakan Anda sedang menguji
C()
, yang tergantung padaA()
danB()
, masing-masing memiliki dependensi sendiri. IMO tergantung pada apa yang coba Anda capai.Jika Anda menguji perilaku perilaku
C()
diketahui yang diketahuiA()
danB()
kemudian mungkin paling mudah dan terbaik untuk mematikanA()
danB()
. Ini mungkin akan disebut unit test oleh puritan.Jika Anda menguji perilaku seluruh sistem (dari
C()
perspektif), saya akan pergiA()
danB()
menerapkan dan mematikan dependensi mereka (jika memungkinkan untuk menguji) atau mengatur lingkungan kotak pasir, seperti tes basis data. Saya akan menyebutnya tes integrasi.Kedua pendekatan ini mungkin valid, jadi jika itu dirancang sehingga Anda dapat mengujinya di muka mungkin akan lebih baik dalam jangka panjang.
sumber