Saya sedang belajar Go dengan mengkode proyek pribadi kecil. Meskipun kecil, saya memutuskan untuk melakukan pengujian unit yang ketat untuk mempelajari kebiasaan baik di Go sejak awal.
Tes unit sepele semuanya baik-baik saja dan keren, tapi saya bingung dengan ketergantungan sekarang; Saya ingin dapat mengganti beberapa panggilan fungsi dengan yang tiruan. Berikut cuplikan kode saya:
func get_page(url string) string {
get_dl_slot(url)
defer free_dl_slot(url)
resp, err := http.Get(url)
if err != nil { return "" }
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil { return "" }
return string(contents)
}
func downloader() {
dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
content := get_page(BASE_URL)
links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
matches := links_regexp.FindAllStringSubmatch(content, -1)
for _, match := range matches{
go serie_dl(match[1], match[2])
}
}
Saya ingin dapat menguji pengunduh () tanpa benar-benar mendapatkan halaman melalui http - yaitu dengan mengejek get_page (lebih mudah karena hanya mengembalikan konten halaman sebagai string) atau http.Get ().
Saya menemukan utas ini: https://groups.google.com/forum/#!topic/golang-nuts/6AN1E2CJOxI yang tampaknya merupakan masalah serupa. Julian Phillips menyajikan perpustakaannya, Withmock ( http://github.com/qur/withmock ) sebagai solusi, tetapi saya tidak dapat membuatnya berfungsi. Inilah bagian yang relevan dari kode pengujian saya, yang sebagian besar adalah kode pemujaan kargo bagi saya, jujur:
import (
"testing"
"net/http" // mock
"code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
ctrl := gomock.NewController()
defer ctrl.Finish()
http.MOCK().SetController(ctrl)
http.EXPECT().Get(BASE_URL)
downloader()
// The rest to be written
}
Output tes berikut:
ERROR: Failed to install '_et/http': exit status 1
output:
can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in /var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http
Apakah Withmock solusi untuk masalah pengujian saya? Apa yang harus saya lakukan untuk membuatnya bekerja?
sumber
Jawaban:
Kudos kepada Anda untuk berlatih pengujian yang baik! :)
Secara pribadi, saya tidak menggunakan
gomock
(atau kerangka kerja mengejek dalam hal ini; mengejek di Go sangat mudah tanpa itu). Saya akan meneruskan dependensi kedownloader()
fungsi sebagai parameter, atau saya akan membuatdownloader()
metode pada tipe, dan tipe tersebut dapat menahanget_page
dependensi:Metode 1: Lulus
get_page()
sebagai parameterdownloader()
Utama:
Uji:
Method2: Buat
download()
metode tipeDownloader
:Jika Anda tidak ingin meneruskan ketergantungan sebagai parameter, Anda juga bisa membuat
get_page()
anggota tipe, dan membuatdownload()
metode tipe itu, yang kemudian dapat menggunakanget_page
:Utama:
Uji:
sumber
Jika Anda mengubah definisi fungsi Anda menggunakan variabel sebagai gantinya:
Anda dapat menimpanya dalam tes Anda:
Hati-hati, tes Anda yang lain mungkin gagal jika mereka menguji fungsi dari fungsi yang Anda timpa!
Penulis Go menggunakan pola ini di pustaka Go standar untuk memasukkan kait uji ke dalam kode untuk mempermudah pengujian:
https://golang.org/src/net/hook.go
https://golang.org/src/net/dial.go#L248
https://golang.org/src/net/dial_test.go#L701
sumber
Saya menggunakan pendekatan yang sedikit berbeda di mana metode struct publik mengimplementasikan antarmuka tetapi logika mereka terbatas hanya membungkus fungsi pribadi (tidak diekspor) yang menggunakan antarmuka tersebut sebagai parameter. Ini memberi Anda perincian yang Anda perlukan untuk mengejek hampir semua ketergantungan namun memiliki API bersih untuk digunakan dari luar ruang uji Anda.
Untuk memahami ini, sangat penting untuk memahami bahwa Anda memiliki akses ke metode
_test.go
yang tidak diekspor dalam kasus pengujian Anda (yaitu dari dalam file Anda ) sehingga Anda menguji mereka alih-alih menguji yang diekspor yang tidak memiliki logika di dalam selain membungkus.Untuk meringkas: uji fungsi yang tidak diekspor daripada menguji yang diekspor!
Mari kita buat contoh. Katakanlah kita memiliki struct Slack API yang memiliki dua metode:
SendMessage
metode yang mengirimkan permintaan HTTP ke WebHook SlackSendDataSynchronously
metode yang diberikan sepotong string iterates atas mereka dan panggilanSendMessage
untuk setiap iterasiJadi untuk menguji
SendDataSynchronously
tanpa membuat permintaan HTTP setiap kali kita harus mengejekSendMessage
, kan?Apa yang saya sukai dari pendekatan ini adalah bahwa dengan melihat metode yang tidak diekspor, Anda dapat melihat dengan jelas apa dependensinya. Pada saat yang sama API yang Anda ekspor jauh lebih bersih dan dengan lebih sedikit parameter untuk diteruskan karena ketergantungan sebenarnya di sini adalah hanya penerima induk yang mengimplementasikan semua antarmuka itu sendiri. Namun setiap fungsi berpotensi bergantung hanya pada satu bagian saja (satu, mungkin dua antarmuka) yang membuat refactor jauh lebih mudah. Sangat menyenangkan untuk melihat bagaimana kode Anda benar-benar digabungkan hanya dengan melihat tanda tangan fungsi, saya pikir itu membuat alat yang ampuh terhadap kode berbau.
Untuk mempermudah, saya meletakkan semuanya ke dalam satu file untuk memungkinkan Anda menjalankan kode di taman bermain di sini, tetapi saya sarankan Anda juga memeriksa contoh lengkapnya di GitHub, di sini adalah file slack.go dan di sini slack_test.go .
Dan di sini semuanya :)
sumber
Saya akan melakukan sesuatu seperti,
Utama
Uji
Dan saya akan menghindari
_
di golang. Lebih baik gunakan camelCasesumber
p := patch(mockGetPage, getPage); defer p.done()
. Saya baru mulai, dan mencoba melakukan ini menggunakanunsafe
perpustakaan, tetapi sepertinya tidak mungkin dilakukan dalam kasus umum.Peringatan: Ini mungkin sedikit mengembang ukuran file yang dapat dieksekusi dan biaya kinerja runtime sedikit. IMO, ini akan lebih baik jika golang memiliki fitur seperti penghias makro atau fungsi.
Jika Anda ingin mengolok-olok fungsi tanpa mengubah API-nya, cara termudah adalah dengan sedikit mengubah implementasinya:
Dengan cara ini kita benar-benar dapat mengejek satu fungsi dari yang lain. Untuk lebih nyaman kami dapat menyediakan boilerplate mengejek seperti:
Dalam file uji:
sumber
Mengingat unit test adalah domain dari pertanyaan ini, sangat disarankan Anda untuk menggunakan https://github.com/bouk/monkey . Paket ini membuat Anda untuk mengejek tes tanpa mengubah kode sumber asli Anda. Bandingkan dengan jawaban lain, ini lebih "tidak mengganggu"。
UTAMA
UJI MOCK
Sisi buruknya adalah:
- Diingatkan oleh Dave.C, Metode ini tidak aman. Jadi jangan menggunakannya di luar unit test.
- Apakah Go non-idiomatik.
Sisi baiknya adalah:
++ Tidak mengganggu. Membuat Anda melakukan sesuatu tanpa mengubah kode utama. Seperti yang dikatakan Thomas.
++ Membuat Anda mengubah perilaku paket (mungkin disediakan oleh pihak ketiga) dengan kode paling sedikit.
sumber