Tests unitaires locaux pour Go

Les tests unitaires locaux s'exécutent dans votre environnement sans accéder aux composants distants. App Engine propose des utilitaires de test qui s'appuient sur des mises en œuvre locales de Cloud Datastore et d'autres services App Engine.

Les services de développement simulent le comportement du service réel localement à des fins de test. Par exemple, l'utilisation du datastore indiquée dans la section Écrire des tests pour le datastore et le cache mémoire vous permet de tester le code de votre datastore, sans effectuer de requête auprès du véritable datastore. Toute entité stockée lors d'un test unitaire du datastore est stockée en local pour ensuite être supprimée après le test. Vous pouvez effectuer de petits tests rapides sans aucune dépendance sur le datastore.

Ce document explique comment écrire des tests unitaires sur des services App Engine locaux à l'aide du package testing Go.

Le package testing Go

Vous pouvez automatiser le téléchargement, la création et le test des packages Go à l'aide de l'outil goapp. goapp fait partie du SDK App Engine pour Go.

La commande goapp test et le package testing Go standard peuvent être utilisés en association pour exécuter des tests unitaires avec votre code d'application. Pour en savoir plus sur les tests avec Go, reportez-vous à la section Testing de la page How to Write Go Code et à la documentation de référence sur le package testing.

Les tests unitaires sont contenus dans des fichiers se terminant par le suffixe _test.go. Par exemple, supposons que vous souhaitiez tester une fonction nommée composeNewsletter qui renvoie un objet *mail.Message. Le fichier newsletter_test.go suivant montre un test simple pour cette fonction :

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)
	}
}

Ce test serait appelé à l'aide de la commande goapp test depuis le répertoire du package :

goapp test

L'outil goapp se trouve dans le répertoire racine du SDK App Engine. Nous vous recommandons de placer ce répertoire dans la variable PATH de votre système pour simplifier l'exécution des tests.

Package aetest

De nombreux appels de fonction aux services App Engine nécessitent un argument context.Context. Le package appengine/aetest fourni avec le SDK vous permet de créer un faux context.Context pour exécuter vos tests à l'aide des services fournis dans l'environnement de développement.

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.
	// ...
}

L'appel à aetest.NewContext lancera dev_appserver.py dans un sous-processus, qui sera utilisé pour traiter les appels d'API pendant le test. Ce sous-processus sera arrêté avec l'appel à done.

Pour plus de contrôle sur l'instance sous-jacente, vous pouvez utiliser plutôt aetest.NewInstance. Cela vous permet de créer plusieurs contextes et de les associer à des objets http.Request.

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.
	// ...
}

Lisez la documentation de référence du package aetest pour plus d'informations.

Écrire des tests Cloud Datastore et memcache

Il est facile de tester le code utilisant le datastore ou Memcache lorsque vous créez un context.Context avec le package aetest : dans votre test, appelez aetest.NewContext pour créer un contexte à transmettre à la fonction testée.

L'application de démonstration transaction dans le SDK contient un exemple de structuration de code à des fins de testabilité, et explique comment tester le code utilisant le datastore :

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)
	}
}

Ce test peut être exécuté à l'aide de la commande goapp test :

goapp test ./demos/transaction

Les tests pour memcache suivent le même schéma : définissez l'état initial du memcache dans votre test, exécutez la fonction à tester et vérifiez que la fonction a interrogé/modifié le memcache comme vous le souhaitez.