Lokale Einheitentests für Go

Lokale Einheitentests werden innerhalb Ihrer Umgebung ausgeführt, ohne auf Remotekomponenten zuzugreifen. App Engine stellt Testdienstprogramme bereit, die lokale Implementierungen von Cloud Datastore und anderen App Engine-Diensten verwenden.

Die Entwicklungsdienste simulieren das Verhalten des tatsächlichen Dienstes lokal zu Testzwecken. Mit der Datenspeicherverwendung, die in Cloud Datastore- und Memcache-Tests verfassen dargestellt wird, können Sie beispielsweise Ihren Datenspeichercode testen, ohne Requests an den realen Datenspeicher zu stellen. Jede während eines Datastore-Einheitentests gespeicherte Entität wird lokal gespeichert und nach dem Testlauf gelöscht. Sie können kurze, schnelle Tests ohne Abhängigkeit vom Datenspeicher selbst ausführen.

In diesem Dokument wird beschrieben, wie Einheitentests für lokale App Engine-Dienste mit dem Go-Testpaket verfasst werden.

Das Go-Testpaket

Sie können das Herunterladen, Erstellen und Testen von Go-Paketen mit dem goapp-Tool automatisieren. goapp ist Teil des App Engine SDK for Go.

Mit dem Befehl goapp test und dem Go-Standardpaket testing können Sie Einheitentests für Ihren Anwendungscode ausführen. Hintergrundinformationen zum Testen mit Go finden Sie im Abschnitt "Testen" der Anleitung zum Schreiben von Go-Code und in der Referenz zum Testpaket.

Einheitentests werden in Dateien mit dem Suffix _test.go abgelegt. Beispiel: Sie möchten eine Funktion mit dem Namen composeNewsletter testen, die eine *mail.Message zurückgibt. Die folgende newsletter_test.go-Datei zeigt einen einfachen Test für diese Funktion:

package newsletter

import (
	"reflect"
	"testing"

	"google.golang.org/appengine/mail"
)

func TestComposeNewsletter(t *testing.T) {
	want := &mail.Message{
		Sender:  "newsletter@appspot.com",
		To:      []string{"User <user@example.com>"},
		Subject: "Weekly App Engine Update",
		Body:    "Don't forget to test your code!",
	}
	if msg := composeNewsletter(); !reflect.DeepEqual(msg, want) {
		t.Errorf("composeMessage() = %+v, want %+v", msg, want)
	}
}

Dieser Test wird mit dem Befehl goapp test im Verzeichnis des Pakets aufgerufen:

goapp test

Das goapp-Tool befindet sich im Stammverzeichnis des App Engine SDK. Damit die Ausführung von Tests vereinfacht wird, sollten Sie dieses Verzeichnis in die Variable PATH Ihres Systems einfügen.

Das Paket aetest

Viele Funktionsaufrufe für App Engine-Dienste erfordern das Argument context.Context als Argument. Das mit dem SDK bereitgestellte Paket appengine/aetest ermöglicht Ihnen das Erstellen eines fiktiven context.Context für die Ausführung Ihrer Tests mit den in der Entwicklungsumgebung bereitgestellten Diensten.

import (
	"testing"

	"google.golang.org/appengine/aetest"
)

func TestWithContext(t *testing.T) {
	ctx, done, err := aetest.NewContext()
	if err != nil {
		t.Fatal(err)
	}
	defer done()

	// Run code and tests requiring the context.Context using ctx.
	// ...
}

Der Aufruf von aetest.NewContext startet dev_appserver.py in einem Unterprozess, der während des Tests API-Aufrufe verarbeitet. Dieser Unterprozess wird mit dem Aufruf von done beendet.

Für mehr Kontrolle über die zugrunde liegende Instanz können Sie stattdessen aetest.NewInstance verwenden. Auf diese Weise können Sie mehrere Kontexte erstellen und diese mit http.Request-Objekten verknüpfen.

import (
	"errors"
	"testing"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/aetest"
	"google.golang.org/appengine/datastore"
)

func TestMyFunction(t *testing.T) {
	inst, err := aetest.NewInstance(nil)
	if err != nil {
		t.Fatalf("Failed to create instance: %v", err)
	}
	defer inst.Close()

	req1, err := inst.NewRequest("GET", "/gophers", nil)
	if err != nil {
		t.Fatalf("Failed to create req1: %v", err)
	}
	c1 := appengine.NewContext(req1)

	req2, err := inst.NewRequest("GET", "/herons", nil)
	if err != nil {
		t.Fatalf("Failed to create req2: %v", err)
	}
	c2 := appengine.NewContext(req2)

	// Run code and tests with *http.Request req1 and req2,
	// and context.Context c1 and c2.
	// ...
}

Weitere Informationen finden Sie in der Referenz zum Paket aetest.

Cloud Datastore- und Memcache-Tests verfassen

Das Testen von Code, bei dem der Datenspeicher oder Memcache verwendet wird, ist einfach, wenn Sie context.Context mit dem Paket aetest erstellen: in Ihrem Testaufruf aetest.NewContext, um einen Kontext zu erstellen, der an die Funktion unter Prüfung.

Die Demoanwendung transaction im SDK enthält ein Beispiel für eine Codestruktur, die Testbarkeit ermöglicht, sowie für das Testen von Code, der den Datenspeicher verwendet:

func TestWithdrawLowBal(t *testing.T) {
	ctx, done, err := aetest.NewContext()
	if err != nil {
		t.Fatal(err)
	}
	defer done()
	key := datastore.NewKey(ctx, "BankAccount", "", 1, nil)
	if _, err := datastore.Put(ctx, key, &BankAccount{100}); err != nil {
		t.Fatal(err)
	}

	err = withdraw(ctx, "myid", 128, 0)
	if err == nil || err.Error() != "insufficient funds" {
		t.Errorf("Error: %v; want insufficient funds error", err)
	}

	b := BankAccount{}
	if err := datastore.Get(ctx, key, &b); err != nil {
		t.Fatal(err)
	}
	if bal, want := b.Balance, 100; bal != want {
		t.Errorf("Balance %d, want %d", bal, want)
	}
}

Dieser Test kann mit dem Befehl goapp test ausgeführt werden:

goapp test ./demos/transaction

Memcache-Tests verlaufen genauso: Richten Sie den Anfangszustand des Memcache in Ihrem Test ein, führen Sie die zu testende Funktion aus und prüfen Sie, ob die Funktion den Memcache wie gewünscht abgefragt/geändert hat.