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:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
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:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
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?
- Buat tes berjalan secara konsisten (hindari mengakses hal-hal yang berubah dari lari ke lari)
- Hindari mengakses sumber daya yang mahal (jangan tekan layanan pihak ketiga, dll.)
- Sederhanakan sistem yang sedang diuji
- Buat lebih mudah untuk menguji semua skenario yang mungkin (yaitu hal-hal seperti mensimulasikan kegagalan, dll)
- Hindari bergantung pada detail potongan kode lain sehingga perubahan pada potongan kode lainnya tidak akan merusak tes ini.
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:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
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.