Ich lerne Go, indem ich ein kleines persönliches Projekt codiere. Obwohl es klein ist, habe ich mich entschlossen, strenge Unit-Tests durchzuführen, um von Anfang an gute Gewohnheiten auf Go zu lernen.
Triviale Unit-Tests waren alle in Ordnung und gut, aber ich bin jetzt verwirrt über Abhängigkeiten. Ich möchte in der Lage sein, einige Funktionsaufrufe durch Scheinaufrufe zu ersetzen. Hier ist ein Ausschnitt aus meinem Code:
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])
}
}
Ich möchte in der Lage sein, downloader () zu testen, ohne tatsächlich eine Seite über http zu erhalten - dh indem ich entweder get_page (einfacher, da nur der Seiteninhalt als Zeichenfolge zurückgegeben wird) oder http.Get () verspotte.
Ich habe diesen Thread gefunden: https://groups.google.com/forum/#!topic/golang-nuts/6AN1E2CJOxI, bei dem es sich anscheinend um ein ähnliches Problem handelt. Julian Phillips präsentiert seine Bibliothek Withmock ( http://github.com/qur/withmock ) als Lösung, aber ich kann sie nicht zum Laufen bringen. Hier sind die relevanten Teile meines Testcodes, der für mich größtenteils Frachtkultcode ist, um ehrlich zu sein:
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
}
Die Testausgabe lautet wie folgt:
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
Ist der Withmock eine Lösung für mein Testproblem? Was soll ich tun, damit es funktioniert?
quelle
Antworten:
Ein großes Lob an Sie für das Üben guter Tests! :) :)
Persönlich verwende ich kein
gomock
(oder irgendein spöttisches Framework für dieses Thema; das Verspotten in Go ist ohne es sehr einfach). Ich würde entweder eine Abhängigkeit an diedownloader()
Funktion als Parameter übergeben oderdownloader()
eine Methode für einen Typ erstellen, und der Typ kann dieget_page
Abhängigkeit enthalten:Methode 1: Übergeben
get_page()
als Parameter vondownloader()
Main:
Prüfung:
Methode 2: Erstellen Sie
download()
eine Methode vom TypDownloader
:Wenn Sie die Abhängigkeit nicht als Parameter übergeben möchten, können Sie auch
get_page()
ein Mitglied eines Typs erstellen unddownload()
eine Methode dieses Typs erstellen, die dann Folgendes verwenden kannget_page
:Main:
Prüfung:
quelle
Wenn Sie Ihre Funktionsdefinition ändern, um stattdessen eine Variable zu verwenden:
Sie können es in Ihren Tests überschreiben:
Vorsicht, Ihre anderen Tests können fehlschlagen, wenn sie die Funktionalität der von Ihnen überschriebenen Funktion testen!
Die Go-Autoren verwenden dieses Muster in der Go-Standardbibliothek, um Test-Hooks in Code einzufügen, um das Testen zu vereinfachen:
https://golang.org/src/net/hook.go
https://golang.org/src/net/dial.go#L248
https://golang.org/src/net/dial_test.go#L701
quelle
Ich verwende einen etwas anderen Ansatz, bei dem öffentliche Strukturmethoden Schnittstellen implementieren , ihre Logik sich jedoch darauf beschränkt, nur private (nicht exportierte) Funktionen zu verpacken, die diese Schnittstellen verwenden als Parameter verwenden. Dies gibt Ihnen die Granularität, die Sie benötigen würden, um praktisch jede Abhängigkeit zu verspotten, und verfügt dennoch über eine saubere API, die Sie von außerhalb Ihrer Testsuite verwenden können.
Um dies zu verstehen, ist es unerlässlich zu verstehen, dass Sie Zugriff auf die nicht exportierten Methoden in Ihrem Testfall haben (dh aus Ihrem Inneren)
_test.go
Dateien heraus), damit Sie diese testen können, anstatt die exportierten zu testen, die neben keine Logik enthalten.Zusammenfassend: Testen Sie die nicht exportierten Funktionen, anstatt die exportierten zu testen!
Lassen Sie uns ein Beispiel machen. Angenommen, wir haben eine Slack-API-Struktur mit zwei Methoden:
SendMessage
Methode, die eine HTTP-Anforderung an einen Slack-Webhook sendetSendDataSynchronously
Methode, bei der ein Teil der Zeichenfolgen angegeben wurde, iteriert über diese und fordertSendMessage
jede Iteration aufUm zu testen,
SendDataSynchronously
ohne jedes Mal eine HTTP-Anfrage zu stellen, müssten wir uns verspottenSendMessage
, oder?Was mir an diesem Ansatz gefällt, ist, dass Sie anhand der nicht exportierten Methoden klar erkennen können, welche Abhängigkeiten bestehen. Gleichzeitig ist die API, die Sie exportieren, viel sauberer und es müssen weniger Parameter weitergegeben werden, da die wahre Abhängigkeit hier nur der übergeordnete Empfänger ist, der alle diese Schnittstellen selbst implementiert. Jede Funktion hängt jedoch möglicherweise nur von einem Teil davon ab (eine, möglicherweise zwei Schnittstellen), was Refaktoren viel einfacher macht. Es ist schön zu sehen, wie Ihr Code wirklich gekoppelt ist, wenn Sie sich nur die Funktionssignaturen ansehen. Ich denke, es ist ein leistungsstarkes Werkzeug gegen das Riechen von Code.
Um die Sache zu vereinfachen, habe ich alles in einer Datei zusammengefasst, damit Sie den Code auf dem Spielplatz hier ausführen können. Ich schlage jedoch vor, dass Sie sich auch das vollständige Beispiel auf GitHub ansehen . Hier ist die Datei slack.go und hier slack_test.go .
Und hier das Ganze :)
quelle
Ich würde so etwas tun,
Main
Prüfung
Und ich würde
_
in Golang vermeiden . Verwenden Sie besser camelCasequelle
p := patch(mockGetPage, getPage); defer p.done()
. Ich bin neu und habe versucht, dies über dieunsafe
Bibliothek zu tun, aber im allgemeinen Fall scheint dies unmöglich zu sein.Warnung: Dies kann die Größe der ausführbaren Datei etwas erhöhen und die Laufzeitleistung beeinträchtigen. IMO, das wäre besser, wenn Golang solche Funktionen wie Makro oder Funktionsdekorateur hat.
Wenn Sie Funktionen verspotten möchten, ohne die API zu ändern, können Sie die Implementierung am einfachsten ein wenig ändern:
Auf diese Weise können wir tatsächlich eine Funktion aus den anderen heraus verspotten. Zur Vereinfachung können wir eine solche spöttische Kesselplatte bereitstellen:
In der Testdatei:
quelle
In Anbetracht der Tatsache, dass Unit-Test die Domäne dieser Frage ist, empfehlen wir Ihnen dringend, https://github.com/bouk/monkey zu verwenden . Mit diesem Paket können Sie Tests testen, ohne Ihren ursprünglichen Quellcode zu ändern. Im Vergleich zu anderen Antworten ist es eher "nicht aufdringlich"。
MAIN
SIMULIERTER TEST
Schlechte Seite ist:
- Von Dave.C erinnert, ist diese Methode unsicher. Verwenden Sie es also nicht außerhalb des Unit-Tests.
- Ist nicht idiomatisch Go.
Gute Seite ist:
++ Ist nicht aufdringlich. Lassen Sie Dinge tun, ohne den Hauptcode zu ändern. Wie Thomas sagte.
++ Lassen Sie das Verhalten des Pakets (möglicherweise von Drittanbietern bereitgestellt) mit dem geringsten Code ändern.
quelle