Saya memiliki kelas yang refactored dalam 1 kelas utama dan 2 kelas lebih kecil. Kelas-kelas utama menggunakan database (seperti banyak kelas saya lakukan) dan mengirim email. Jadi kelas utama memiliki IPersonRepository
dan IEmailRepository
menyuntikkan yang pada gilirannya mengirim ke 2 kelas yang lebih kecil.
Sekarang saya ingin menguji unit kelas utama, dan telah belajar untuk tidak melepas pekerjaan internal kelas, karena kita harus dapat mengubah kerja internal tanpa melanggar tes unit.
Tetapi karena kelas menggunakan IPersonRepository
dan IEmailRepository
, saya HARUS menentukan hasil (tiruan / tiruan) untuk beberapa metode untuk IPersonRepository
. Kelas utama menghitung beberapa data berdasarkan data yang ada dan mengembalikannya. Jika saya ingin menguji itu, saya tidak melihat bagaimana saya bisa menulis tes tanpa menentukan bahwa IPersonRepository.GetSavingsByCustomerId
pengembalian x. Tapi kemudian unit test saya 'tahu' tentang cara kerja internal, karena 'tahu' metode mana yang harus diejek dan mana yang tidak.
Bagaimana saya bisa menguji kelas yang telah menyuntikkan dependensi, tanpa tes mengetahui tentang internal?
Latar Belakang:
Dalam pengalaman saya banyak tes seperti ini membuat tiruan untuk repositori dan kemudian memberikan data yang tepat untuk mengejek atau menguji apakah metode tertentu dipanggil selama eksekusi. Either way, tes tahu tentang internal.
Sekarang saya telah melihat presentasi tentang teori (yang saya dengar sebelumnya) bahwa tes tidak boleh tahu tentang implementasinya. Pertama karena Anda tidak menguji cara kerjanya, tetapi juga karena ketika Anda sekarang mengubah implementasi semua tes unit gagal karena mereka 'tahu' tentang implementasi. Sementara saya suka konsep pengujian tidak menyadari implementasi, saya tidak tahu bagaimana mencapainya.
sumber
IPersonRepository
objek, antarmuka itu dan semua metode yang dijelaskannya tidak "internal" lagi, jadi itu bukan masalah tes. Pertanyaan Anda semestinya adalah "bagaimana saya bisa mengubah kelas menjadi unit yang lebih kecil tanpa mengekspos terlalu banyak di depan umum". Jawabannya adalah "menjaga antarmuka yang ramping" (dengan berpegang teguh pada prinsip pemisahan antarmuka, misalnya). Itulah IMHO titik 2 dalam jawaban @ DavidArno (saya kira tidak perlu bagi saya untuk mengulanginya di jawaban lain).Jawaban:
Anda benar bahwa ini merupakan pelanggaran terhadap prinsip "jangan menguji internal" dan merupakan prinsip umum yang diabaikan orang.
Ada dua solusi yang dapat Anda adopsi untuk mengatasi pelanggaran ini:
1) Memberikan tiruan lengkap
IPersonRepository
. Pendekatan Anda saat ini dijelaskan adalah untuk memasangkan tiruan ke pekerjaan dalam metode yang diuji dengan hanya mengejek metode yang akan memanggil. Jika Anda memasok tiruan untuk semua metodeIPersonRepository
, maka Anda menghapus kopling itu. Cara kerja bagian dalam dapat berubah tanpa memengaruhi tiruan, sehingga membuat tes tidak terlalu rapuh.Pendekatan ini memiliki keuntungan menjaga mekanisme DI tetap sederhana, tetapi dapat membuat banyak pekerjaan jika antarmuka Anda mendefinisikan banyak metode.
2) Jangan menyuntikkan
IPersonRepository
, menyuntikkanGetSavingsByCustomerId
metode, atau menyuntikkan nilai tabungan. Masalah dengan menyuntikkan seluruh implementasi antarmuka adalah bahwa Anda kemudian menyuntikkan ("katakan, jangan tanya") sebuah sistem "tanya, jangan katakan", mencampurkan kedua pendekatan. Jika Anda mengambil pendekatan "DI murni", metode harus diberikan (diceritakan) metode yang tepat untuk memanggil jika ingin nilai tabungan, daripada diberikan objek (yang kemudian harus secara efektif meminta metode untuk memanggil).Keuntungan dari pendekatan ini adalah bahwa ia menghindari perlunya mengejek (di luar metode pengujian yang Anda masukkan ke dalam metode yang diuji). Kerugiannya adalah bahwa hal itu dapat menyebabkan tanda tangan metode berubah sebagai respons terhadap persyaratan perubahan metode.
Kedua pendekatan memiliki pro dan kontra, jadi pilihlah yang paling sesuai dengan kebutuhan Anda.
sumber
Pendekatan saya adalah membuat versi 'tiruan' dari repositori yang membaca dari file sederhana yang berisi data yang dibutuhkan.
Ini berarti tes individu tidak 'tahu' tentang pengaturan mock, meskipun jelas proyek tes keseluruhan akan merujuk mock dan memiliki file pengaturan dll.
Ini menghindari pengaturan objek tiruan kompleks yang diperlukan oleh kerangka kerja tiruan dan memungkinkan Anda untuk menggunakan objek 'tiruan' dalam contoh nyata aplikasi Anda untuk pengujian UI dan sejenisnya.
Karena 'tiruan' sepenuhnya diimplementasikan, alih-alih pengaturan khusus untuk skenario pengujian Anda, perubahan implementasi, misalnya katakanlah GetSavingsForCustomer sekarang juga harus menghapus pelanggan. Tidak akan merusak tes (kecuali tentu saja itu benar-benar merusak tes) Anda hanya perlu memperbarui implementasi tiruan tunggal Anda dan semua tes Anda akan berjalan melawannya tanpa mengubah pengaturan mereka
sumber
Tes unit biasanya berupa tes papan tulis (Anda memiliki akses ke kode asli). Oleh karena itu tidak apa-apa untuk mengetahui internal sampai batas tertentu, namun untuk pemula lebih mudah untuk tidak, karena Anda tidak boleh menguji perilaku internal (seperti "metode panggilan pertama, lalu b dan kemudian lagi").
Menyuntikkan tiruan yang menyediakan data tidak masalah, karena kelas Anda (unit) tergantung pada data eksternal tersebut (atau penyedia data). Namun, Anda harus memverifikasi hasilnya (bukan cara untuk sampai ke sana)! mis. Anda memberikan contoh Orang dan memverifikasi bahwa email dikirim ke alamat email yang benar, misalnya dengan memberikan ejekan untuk email juga, bahwa ejekan itu tidak melakukan apa-apa selain menyimpan alamat e-mail orang yang dituju untuk kemudian diakses oleh tes Anda -kode. (Saya pikir Martin Fowler menyebut mereka bertopik daripada mengejek, meskipun)
sumber