Saya memiliki pemahaman dasar mengenai benda tiruan dan benda palsu, tetapi saya tidak yakin apakah saya memiliki perasaan tentang kapan / di mana menggunakan ejekan - terutama karena akan berlaku untuk skenario ini di sini .
Saya memiliki pemahaman dasar mengenai benda tiruan dan benda palsu, tetapi saya tidak yakin apakah saya memiliki perasaan tentang kapan / di mana menggunakan ejekan - terutama karena akan berlaku untuk skenario ini di sini .
Jawaban:
Tes unit harus menguji codepath tunggal melalui metode tunggal. Ketika eksekusi suatu metode melewati di luar metode itu, ke objek lain, dan kembali lagi, Anda memiliki ketergantungan.
Ketika Anda menguji jalur kode dengan ketergantungan yang sebenarnya, Anda bukan pengujian unit; Anda sedang menguji integrasi. Meskipun bagus dan perlu, itu bukan pengujian unit.
Jika ketergantungan Anda buggy, tes Anda dapat dipengaruhi sedemikian rupa untuk mengembalikan false positive. Misalnya, Anda dapat meneruskan dependensi ke null yang tidak terduga, dan dependensi mungkin tidak menghasilkan null seperti yang didokumentasikan harus dilakukan. Tes Anda tidak menemukan pengecualian argumen nol sebagaimana mestinya, dan tes berlalu.
Juga, Anda mungkin menemukan kesulitan, jika bukan tidak mungkin, untuk secara andal mendapatkan objek dependen untuk mengembalikan apa yang Anda inginkan selama tes. Itu juga termasuk melempar pengecualian yang diharapkan dalam tes.
Mock menggantikan ketergantungan itu. Anda menetapkan harapan pada panggilan ke objek dependen, menetapkan nilai pengembalian tepat yang harus diberikan untuk melakukan tes yang Anda inginkan, dan / atau pengecualian apa yang dilemparkan sehingga Anda dapat menguji kode penanganan pengecualian Anda. Dengan cara ini Anda dapat menguji unit yang dimaksud dengan mudah.
TL; DR: mengejek setiap ketergantungan yang disentuh tes unit Anda.
Objek tiruan berguna ketika Anda ingin menguji interaksi antara kelas yang diuji dan antarmuka tertentu.
Sebagai contoh, kami ingin menguji bahwa sendInvitations(MailServer mailServer)
panggilan metode MailServer.createMessage()
tepat sekali, dan juga panggilan MailServer.sendMessage(m)
tepat sekali, dan tidak ada metode lain yang dipanggil pada MailServer
antarmuka. Inilah saatnya kita bisa menggunakan benda tiruan.
Dengan objek tiruan, alih-alih lulus nyata MailServerImpl
, atau tes TestMailServer
, kita bisa melewati implementasi tiruan dari MailServer
antarmuka. Sebelum kita melewati tiruan MailServer
, kita "melatih" itu, sehingga ia tahu metode apa yang dibutuhkan untuk diharapkan dan nilai pengembalian apa yang akan dikembalikan. Pada akhirnya, objek tiruan menegaskan, bahwa semua metode yang diharapkan disebut seperti yang diharapkan.
Ini kedengarannya bagus secara teori, tetapi ada juga beberapa kerugian.
Jika Anda memiliki kerangka kerja tiruan di tempat, Anda tergoda untuk menggunakan objek tiruan setiap kali Anda harus melewati antarmuka ke kelas yang sedang diuji. Dengan cara ini Anda akhirnya menguji interaksi bahkan ketika itu tidak perlu . Sayangnya, pengujian interaksi yang tidak diinginkan (tidak disengaja) buruk, karena Anda menguji bahwa persyaratan tertentu diimplementasikan dengan cara tertentu, alih-alih implementasi menghasilkan hasil yang diperlukan.
Berikut ini contoh dalam pseudocode. Misalkan kita telah membuat MySorter
kelas dan kami ingin mengujinya:
// the correct way of testing
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert testList equals [1, 2, 3, 7, 8]
}
// incorrect, testing implementation
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert that compare(1, 2) was called once
assert that compare(1, 3) was not called
assert that compare(2, 3) was called once
....
}
(Dalam contoh ini kami mengasumsikan bahwa itu bukan algoritma penyortiran tertentu, seperti penyortiran cepat, yang ingin kami uji; dalam hal ini, tes yang terakhir sebenarnya akan valid.)
Dalam contoh ekstrem seperti itu, jelas mengapa contoh terakhir ini salah. Ketika kami mengubah implementasi MySorter
, tes pertama melakukan pekerjaan yang baik untuk memastikan kami masih mengurutkan dengan benar, yang merupakan inti dari tes - mereka memungkinkan kami untuk mengubah kode dengan aman. Di sisi lain, tes terakhir selalu rusak dan secara aktif berbahaya; itu menghambat refactoring.
Kerangka kerja tiruan sering memungkinkan penggunaan yang tidak terlalu ketat, di mana kita tidak harus menentukan dengan tepat berapa kali metode harus dipanggil dan parameter apa yang diharapkan; mereka memungkinkan membuat benda tiruan yang digunakan sebagai bertopik .
Misalkan kita memiliki metode sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)
yang ingin kita uji. The PdfFormatter
objek dapat digunakan untuk membuat undangan. Inilah tesnya:
testInvitations() {
// train as stub
pdfFormatter = create mock of PdfFormatter
let pdfFormatter.getCanvasWidth() returns 100
let pdfFormatter.getCanvasHeight() returns 300
let pdfFormatter.addText(x, y, text) returns true
let pdfFormatter.drawLine(line) does nothing
// train as mock
mailServer = create mock of MailServer
expect mailServer.sendMail() called exactly once
// do the test
sendInvitations(pdfFormatter, mailServer)
assert that all pdfFormatter expectations are met
assert that all mailServer expectations are met
}
Dalam contoh ini, kami tidak terlalu peduli tentang PdfFormatter
objek, jadi kami hanya melatihnya untuk menerima panggilan apa pun secara diam-diam dan mengembalikan beberapa nilai pengembalian kalengan yang masuk akal untuk semua metode yang sendInvitation()
memanggil pada saat ini. Bagaimana cara kami membuat daftar metode pelatihan ini? Kami hanya menjalankan tes dan terus menambahkan metode sampai tes berlalu. Perhatikan, bahwa kami melatih rintisan untuk merespons suatu metode tanpa memiliki petunjuk mengapa perlu menyebutnya, kami hanya menambahkan semua yang dikeluhkan oleh tes. Kami senang, tes lulus.
Tetapi apa yang terjadi kemudian, ketika kita mengubah sendInvitations()
, atau beberapa kelas lain yang sendInvitations()
menggunakan, untuk membuat lebih banyak pdf mewah? Tes kami tiba-tiba gagal karena sekarang lebih banyak metode PdfFormatter
dipanggil dan kami tidak melatih rintisan kami untuk mengharapkannya. Dan biasanya bukan hanya satu tes yang gagal dalam situasi seperti ini, ini adalah tes apa pun yang menggunakan, secara langsung atau tidak langsung, sendInvitations()
metode tersebut. Kami harus memperbaiki semua tes itu dengan menambahkan lebih banyak pelatihan. Perhatikan juga, bahwa kita tidak dapat menghapus metode yang tidak lagi diperlukan, karena kita tidak tahu mana yang tidak diperlukan. Sekali lagi, ini menghambat refactoring.
Juga, keterbacaan tes sangat menderita, ada banyak kode di sana yang tidak kami tulis karena kami ingin, tetapi karena kami harus; bukan kita yang menginginkan kode itu di sana. Tes yang menggunakan benda tiruan terlihat sangat kompleks dan seringkali sulit dibaca. Tes harus membantu pembaca memahami, bagaimana kelas yang diuji harus digunakan, sehingga harus sederhana dan mudah. Jika tidak dapat dibaca, tidak ada yang akan memeliharanya; pada kenyataannya, lebih mudah untuk menghapusnya daripada mempertahankannya.
Bagaimana cara memperbaikinya? Dengan mudah:
PdfFormatterImpl
. Jika tidak memungkinkan, ubah kelas nyata untuk memungkinkannya. Tidak bisa menggunakan kelas dalam tes biasanya menunjukkan beberapa masalah dengan kelas. Memperbaiki masalah adalah situasi win-win - Anda memperbaiki kelas dan Anda memiliki tes yang lebih sederhana. Di sisi lain, tidak memperbaikinya dan menggunakan ejekan adalah situasi yang tidak dapat dimenangkan - Anda tidak memperbaiki kelas yang sebenarnya dan Anda memiliki tes yang lebih kompleks dan kurang dapat dibaca yang menghambat refactoring lebih lanjut.TestPdfFormatter
yang tidak melakukan apa-apa. Dengan begitu Anda bisa mengubahnya sekali untuk semua tes dan tes Anda tidak berantakan dengan pengaturan panjang di mana Anda melatih bertopik Anda.Secara keseluruhan, benda tiruan dapat digunakan, tetapi ketika tidak digunakan dengan hati-hati, benda tiruan itu sering mendorong praktik buruk, menguji detail implementasi, menghalangi refactoring, dan menghasilkan tes yang sulit dibaca dan sulit untuk mempertahankan tes .
Untuk beberapa detail lebih lanjut tentang kekurangan tiruan lihat juga Obyek Mock: Kekurangan dan Kasus Penggunaan .
Aturan praktis:
Jika fungsi yang Anda uji memerlukan objek yang rumit sebagai parameter, dan akan merepotkan jika hanya instantiate objek ini (jika, misalnya ia mencoba membuat koneksi TCP), gunakan tiruan.
Anda harus mengejek suatu objek ketika Anda memiliki ketergantungan pada unit kode yang Anda coba uji yang perlu "hanya begitu".
Misalnya, ketika Anda mencoba menguji beberapa logika dalam unit kode Anda, tetapi Anda perlu mendapatkan sesuatu dari objek lain dan apa yang dikembalikan dari ketergantungan ini dapat memengaruhi apa yang Anda coba uji - mengejek objek itu.
Podcast hebat tentang topik ini dapat ditemukan di sini