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:
- itu
SendMessage metode yang mengirimkan permintaan HTTP ke WebHook Slack
- yang
SendDataSynchronouslymetode yang diberikan sepotong string iterates atas mereka dan panggilan SendMessageuntuk setiap iterasi
Jadi untuk menguji SendDataSynchronouslytanpa membuat permintaan HTTP setiap kali kita harus mengejek SendMessage, kan?
package main
import (
"fmt"
)
// URI interface
type URI interface {
GetURL() string
}
// MessageSender interface
type MessageSender interface {
SendMessage(message string) error
}
// This one is the "object" that our users will call to use this package functionalities
type API struct {
baseURL string
endpoint string
}
// Here we make API implement implicitly the URI interface
func (api *API) GetURL() string {
return api.baseURL + api.endpoint
}
// Here we make API implement implicitly the MessageSender interface
// Again we're just WRAPPING the sendMessage function here, nothing fancy
func (api *API) SendMessage(message string) error {
return sendMessage(api, message)
}
// We want to test this method but it calls SendMessage which makes a real HTTP request!
// Again we're just WRAPPING the sendDataSynchronously function here, nothing fancy
func (api *API) SendDataSynchronously(data []string) error {
return sendDataSynchronously(api, data)
}
// this would make a real HTTP request
func sendMessage(uri URI, message string) error {
fmt.Println("This function won't get called because we will mock it")
return nil
}
// this is the function we want to test :)
func sendDataSynchronously(sender MessageSender, data []string) error {
for _, text := range data {
err := sender.SendMessage(text)
if err != nil {
return err
}
}
return nil
}
// TEST CASE BELOW
// Here's our mock which just contains some variables that will be filled for running assertions on them later on
type mockedSender struct {
err error
messages []string
}
// We make our mock implement the MessageSender interface so we can test sendDataSynchronously
func (sender *mockedSender) SendMessage(message string) error {
// let's store all received messages for later assertions
sender.messages = append(sender.messages, message)
return sender.err // return error for later assertions
}
func TestSendsAllMessagesSynchronously() {
mockedMessages := make([]string, 0)
sender := mockedSender{nil, mockedMessages}
messagesToSend := []string{"one", "two", "three"}
err := sendDataSynchronously(&sender, messagesToSend)
if err == nil {
fmt.Println("All good here we expect the error to be nil:", err)
}
expectedMessages := fmt.Sprintf("%v", messagesToSend)
actualMessages := fmt.Sprintf("%v", sender.messages)
if expectedMessages == actualMessages {
fmt.Println("Actual messages are as expected:", actualMessages)
}
}
func main() {
TestSendsAllMessagesSynchronously()
}
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 :)