Mengolok-olok, bertopik dan mata-mata di Kerangka Kerja Spock

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 benar
  • FooService.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: "   " // 1 * fooService.doSomething() //        response.text == "Stub did something" } } 

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


Source: https://habr.com/ru/post/id435488/


All Articles