Spock menyediakan 3 alat yang ampuh (tetapi berbeda intinya) yang menyederhanakan tes penulisan: Mock, Stub dan Spy.
Cukup sering, kode yang perlu diuji perlu berinteraksi dengan modul eksternal yang disebut dependensi (artikel asli menggunakan istilah kolaborator, yang tidak terlalu umum di lingkungan berbahasa Rusia).
Tes unit paling sering dirancang untuk menguji satu kelas terisolasi menggunakan berbagai varian mok: Mock, Stub dan Spy. Jadi tes akan lebih dapat diandalkan dan lebih kecil kemungkinannya untuk pecah ketika kode dependensi berkembang.
Tes terisolasi seperti itu kurang rentan terhadap masalah ketika mengubah detail internal implementasi ketergantungan.
Dari seorang penerjemah: setiap kali saya menggunakan Kerangka Kerja Spock untuk menulis tes, saya merasa seperti saya bisa membuat kesalahan ketika memilih cara untuk mengganti dependensi. Artikel ini memiliki lembar contekan sesingkat mungkin untuk memilih mekanisme pembuatan mokas.
TL; DR
Mengejek
Gunakan Mock untuk:
- kontrak cek antara kode tes dan dependensi
- memeriksa bahwa metode ketergantungan disebut jumlah yang benar kali
- validasi parameter yang dengannya kode dependensi dipanggil
Rintisan bertopik
Gunakan Stub untuk:
- memberikan hasil panggilan yang telah ditentukan sebelumnya
- melakukan tindakan yang telah ditentukan sebelumnya yang diharapkan dari dependensi, seperti melempar pengecualian
Mata-mata
Mata-mata Takut. Seperti yang dikatakan dalam dokumentasi Spock:
Berpikir dua kali sebelum menggunakan mekanisme ini. Mungkin Anda harus mendesain ulang solusi Anda dan mengatur ulang kode Anda.
Tetapi kebetulan bahwa ada situasi ketika kita harus bekerja dengan kode warisan. Kode lama bisa sulit atau bahkan tidak mungkin untuk diuji dengan massa dan stub. Dalam hal ini, hanya ada satu solusi: gunakan Spy.
Lebih baik memiliki kode warisan yang ditutupi dengan tes menggunakan Spy daripada tidak memiliki tes warisan sama sekali.
Gunakan Spy untuk:
- menguji kode lama yang tidak dapat diuji dengan metode lain
- memeriksa bahwa metode ketergantungan disebut jumlah yang benar kali
- validasi parameter yang diteruskan
- memberikan respons ketergantungan yang telah ditentukan sebelumnya
- melakukan tindakan yang telah ditetapkan sebagai respons terhadap panggilan ke metode ketergantungan
Mengejek
Semua kekuatan moxas dimanifestasikan ketika tugas uji unit adalah untuk memverifikasi kontrak antara kode yang diuji dan dependensi. Mari kita lihat contoh berikut ini, di mana kita memiliki pengontrol FooController
yang menggunakan FooService
sebagai ketergantungan, dan uji fungsi ini dengan mengejek.
FooController.groovy
package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } }
FooService.groovy
package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } }
Dalam skenario ini, kami ingin menulis tes yang akan menguji:
- kontrak antara
FooController
dan FooService
FooService.doSomething(name)
disebut jumlah kali yang benarFooService.doSomething(name)
dipanggil dengan parameter yang benar
Lihatlah tes ini:
MockSpec.groovy
package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class MockSpec extends Specification implements ControllerUnitTest<FooController> { void "Mock FooService"() { given: " " def fooService = Mock(FooService) and: " " controller.fooService = fooService when: " " controller.doSomething() then: " " 1 * fooService.doSomething("Sally") and: " '' - 'null'" response.text == null.toString() } }
Tes di atas membuat tiruan layanan:
def fooService = Mock(FooService)
Tes ini juga memeriksa apakah FooService.doSomething(name)
dipanggil satu kali, dan parameter yang dilewatinya cocok dengan string "Sally"
.
1 * fooService.doSomething("Sally")
Kode di atas memecahkan 4 masalah penting:
- menciptakan tiruan untuk
FooService
- memverifikasi bahwa
FooService.doSomething(String name)
dipanggil tepat sekali dengan parameter String
dan nilai "Sally"
- mengisolasi kode uji, menggantikan implementasi ketergantungan
Rintisan bertopik
Apakah kode yang diuji menggunakan dependensi? Apakah tujuan pengujian untuk memastikan bahwa kode yang diuji berfungsi dengan benar ketika berinteraksi dengan dependensi? Apakah hasil panggilan ke metode ketergantungan nilai input untuk kode yang diuji?
Jika perilaku kode yang diuji berubah tergantung pada perilaku dependensi, maka Anda harus menggunakan stub (Stub).
Mari kita lihat contoh berikut dengan FooController
dan FooService
dan uji fungsionalitas pengontrol menggunakan bertopik.
FooController.groovy
package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } }
FooService.groovy
package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } }
Kode Tes:
StubSpec.groovy
package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class StubSpec extends Specification implements ControllerUnitTest<FooController> { void "Stub FooService"() { given: " " def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" } and: " " controller.fooService = fooService when: " " controller.doSomething() then: " "
Anda dapat membuat rintisan seperti ini:
def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" }
Kode di atas memecahkan 4 masalah penting:
- menciptakan
FooService
rintisan - memastikan bahwa
FooService.doSomething(String name)
akan mengembalikan string "Stub did something"
FooService.doSomething(String name)
"Stub did something"
terlepas dari parameter yang dikirimkan (jadi kami menggunakan karakter _
) - mengisolasi kode yang diuji, menggantikan implementasi dependensi dengan sebuah rintisan
Mata-mata
Tolong jangan baca bagian ini.
Jangan lihat.
Lewati ke yang berikutnya.
Masih membaca? Kalau begitu, oke, mari kita berurusan dengan Spy.
Jangan gunakan Spy. Seperti yang dikatakan dalam dokumentasi Spock:
Berpikir dua kali sebelum menggunakan mekanisme ini. Mungkin Anda harus mendesain ulang solusi Anda dan mengatur ulang kode Anda.
Dalam hal ini, ada beberapa situasi ketika kita harus bekerja dengan kode lawas. Kode lawas tidak dapat diuji menggunakan massa atau stub. Dalam hal ini, mata-mata tetap menjadi satu-satunya pilihan.
Mata-mata berbeda dari mokas atau bertopik karena tidak berfungsi seperti bertopik.
Ketika ketergantungan diganti dengan mok atau rintisan, objek uji dibuat, dan kode sumber nyata dari ketergantungan tidak dieksekusi.
Mata-mata, di sisi lain, akan mengeksekusi kode sumber utama dari ketergantungan mata-mata yang dibuat, tetapi mata-mata akan memungkinkan Anda untuk mengubah apa yang mata-mata kembali dan memeriksa panggilan metode, seperti halnya mokas dan bertopik. (Karena itulah namanya Spy).
Mari kita lihat contoh FooController
berikut, yang menggunakan FooService
, dan kemudian menguji fungsionalitas dengan mata-mata.
FooController.groovy
package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } }
FooService.groovy
package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } }
Kode Tes:
SpySpec.groovy
package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class SpySpec extends Specification implements ControllerUnitTest<FooController> { void "Spy FooService"() { given: " -" def fooService = Spy(FooService) and: " " controller.fooService = fooService when: " " controller.doSomething() then: " " 1 * fooService.doSomething("Sally") >> "A Spy can modify implementation" and: ' ' response.text == "A Spy can modify implementation" } }
Membuat instance mata-mata cukup sederhana:
def fooService = Spy(FooService)
Dalam kode di atas, mata-mata memungkinkan kita untuk memeriksa panggilan ke FooService.doSomething(name)
, jumlah panggilan dan nilai parameter. Selain itu, mata-mata mengubah implementasi metode untuk mengembalikan nilai yang berbeda.
1 * fooService.doSomething("Sally") >> "A Spy can modify implementation"
Kode di atas memecahkan 4 masalah penting:
- membuat contoh mata-mata untuk
FooService
- memeriksa interaksi ketergantungan
- memeriksa cara kerja aplikasi sesuai dengan hasil panggilan metode ketergantungan tertentu
- mengisolasi kode yang diuji, menggantikan implementasi dependensi dengan sebuah rintisan
Faq
Opsi mana yang digunakan: Mock, Stub atau Spy?
Ini adalah pertanyaan yang dihadapi banyak pengembang. FAQ ini dapat membantu jika Anda tidak yakin pendekatan mana yang digunakan.
T: Apakah tujuan pengujian verifikasi kontrak antara kode yang diuji dan dependensi?
A: Jika Anda menjawab Ya, gunakan Mock
T: Apakah tujuan pengujian untuk memastikan bahwa kode yang diuji berfungsi dengan benar ketika berinteraksi dengan dependensi?
A: Jika Anda menjawab Ya, gunakan rintisan
T: Apakah hasil pemanggilan metode ketergantungan memasukkan nilai untuk kode yang sedang diuji?
A: Jika Anda menjawab Ya, gunakan rintisan
T: Apakah Anda bekerja dengan kode lawas, yang sangat sulit untuk diuji, dan Anda tidak memiliki opsi tersisa?
A: Coba gunakan Spy
Contoh kode
Anda dapat menemukan kode untuk semua contoh dalam artikel ini di:
https://github.com/ddelponte/mock-stub-spy
Tautan yang bermanfaat