Uczę się Go, kodując mały, osobisty projekt. Mimo że jest mały, zdecydowałem się przeprowadzić rygorystyczne testy jednostkowe, aby od samego początku nauczyć się dobrych nawyków w Go.
Zwykłe testy jednostkowe były w porządku i eleganckie, ale teraz jestem zaintrygowany zależnościami; Chcę mieć możliwość zamiany niektórych wywołań funkcji na pozorowane. Oto fragment mojego kodu:
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])
}
}
Chciałbym móc przetestować downloader () bez faktycznego pobierania strony przez http - tj. Przez mockowanie get_page (łatwiejsze, ponieważ zwraca tylko zawartość strony jako ciąg znaków) lub http.Get ().
Znalazłem ten wątek: https://groups.google.com/forum/#!topic/golang-nuts/6AN1E2CJOxI, który wydaje się być o podobnym problemie. Julian Phillips przedstawia swoją bibliotekę Withmock ( http://github.com/qur/withmock ) jako rozwiązanie, ale nie jestem w stanie zmusić go do działania. Oto odpowiednie części mojego kodu testowego, który jest dla mnie w dużej mierze kodem kultu cargo, szczerze mówiąc:
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
}
Wynik testu jest następujący:
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
Czy Withmock jest rozwiązaniem mojego problemu z testowaniem? Co mam zrobić, żeby to zadziałało?
źródło
Odpowiedzi:
Brawa dla Ciebie za wykonanie dobrych testów! :)
Osobiście nie używam
gomock
(ani żadnego mockującego frameworka; mockowanie w Go jest bardzo łatwe bez niego). Albo przekazałbym zależność dodownloader()
funkcji jako parametr, albo zrobiłbymdownloader()
metodę na typie, a typ może przechowywaćget_page
zależność:Metoda 1: Przekaż
get_page()
jako parametrdownloader()
Główny:
Test:
Method2: Utwórz
download()
metodę typuDownloader
:Jeśli nie chcesz przekazywać zależności jako parametru, możesz również utworzyć
get_page()
element członkowski typu i utworzyćdownload()
metodę tego typu, która może następnie użyćget_page
:Główny:
Test:
źródło
Jeśli zmienisz definicję funkcji, aby zamiast tego używała zmiennej:
Możesz to zmienić w swoich testach:
Ostrożnie, inne testy mogą się nie powieść, jeśli sprawdzą funkcjonalność funkcji, którą nadpisujesz!
Autorzy Go używają tego wzorca w bibliotece standardowej Go do wstawiania punktów zaczepienia testowego do kodu, aby ułatwić testowanie:
https://golang.org/src/net/hook.go
https://golang.org/src/net/dial.go#L248
https://golang.org/src/net/dial_test.go#L701
źródło
Używam nieco innego podejścia, w którym metody struktury publicznej implementują interfejsy, ale ich logika ogranicza się do pakowania prywatnych (niewyeksportowanych) funkcji, które przyjmują te interfejsy jako parametry. Daje to szczegółowość, której będziesz potrzebować, aby symulować praktycznie każdą zależność, a jednocześnie mieć czysty interfejs API do użytku spoza zestawu testów.
Aby to zrozumieć, konieczne jest zrozumienie, że masz dostęp do niewyeksportowanych metod w swoim przypadku testowym (tj. Z poziomu
_test.go
plików), więc testujesz je zamiast testować wyeksportowane, które poza opakowaniem nie mają w sobie żadnej logiki.Podsumowując: przetestuj niewyeksportowane funkcje zamiast testować wyeksportowane!
Zróbmy przykład. Powiedzmy, że mamy strukturę Slack API, która ma dwie metody:
SendMessage
metoda, która wysyła żądanie HTTP do webhook SlackSendDataSynchronously
metoda który otrzymał kawałek ciągów iteracje nad nimi oraz wzywaSendMessage
do każdej iteracjiAby więc testować
SendDataSynchronously
bez wysyłania żądania HTTP za każdym razem, musielibyśmy kpićSendMessage
, prawda?W tym podejściu podoba mi się to, że patrząc na niezgłoszone metody, można wyraźnie zobaczyć, jakie są zależności. Jednocześnie eksportowany interfejs API jest dużo bardziej przejrzysty i zawiera mniej parametrów do przekazania, ponieważ prawdziwą zależnością jest tutaj tylko odbiornik nadrzędny, który sam implementuje wszystkie te interfejsy. Jednak każda funkcja jest potencjalnie zależna tylko od jednej jej części (jednego, może dwóch interfejsów), co znacznie ułatwia refaktory. Miło jest zobaczyć, jak Twój kod jest naprawdę połączony, po prostu patrząc na sygnatury funkcji, myślę, że jest to potężne narzędzie przeciwko wąchaniu kodu.
Aby ułatwić sprawę, umieściłem wszystko w jednym pliku, aby umożliwić uruchomienie kodu na placu zabaw tutaj, ale sugeruję również sprawdzenie pełnego przykładu na GitHub, tutaj jest plik slack.go , a tutaj slack_test.go .
A tu całość :)
źródło
Zrobiłbym coś takiego,
Główny
Test
I unikałbym
_
w golang. Lepiej użyj camelCaseźródło
p := patch(mockGetPage, getPage); defer p.done()
. Jestem nowy i próbowałem to zrobić przy użyciuunsafe
biblioteki, ale w ogólnym przypadku wydaje się to niemożliwe.Ostrzeżenie: może to nieco zwiększyć rozmiar pliku wykonywalnego i obniżyć wydajność w czasie wykonywania. IMO, byłoby lepiej, gdyby golang miał taką funkcję jak makro lub dekorator funkcji.
Jeśli chcesz mockować funkcje bez zmiany ich API, najłatwiej jest trochę zmienić implementację:
W ten sposób możemy faktycznie wyszydzać jedną funkcję spośród innych. Dla wygodniejszego możemy dostarczyć takie szydercze schematy:
W pliku testowym:
źródło
Biorąc pod uwagę, że test jednostkowy jest domeną tego pytania, zdecydowanie zalecamy skorzystanie z https://github.com/bouk/monkey . Ten pakiet umożliwia testowanie próbne bez zmiany oryginalnego kodu źródłowego. W porównaniu z inną odpowiedzią, jest bardziej „nieinwazyjna”。
GŁÓWNY
TEST PRÓBNY
Zła strona to:
- Przypomniał Dave.C, Ta metoda jest niebezpieczna. Więc nie używaj go poza testem jednostkowym.
- Nie idiomatycznie Go.
Dobra strona to:
++ Nie inwazyjny. Spraw, abyś robił rzeczy bez zmiany głównego kodu. Jak powiedział Thomas.
++ Spraw, abyś zmienił zachowanie pakietu (może dostarczonego przez osobę trzecią) przy użyciu najmniejszego kodu.
źródło