Test delle unità locali per Go

I test delle unità locali vengono eseguiti all'interno del tuo ambiente senza accedere ai componenti remoti. App Engine fornisce utilità di test che utilizzano le implementazioni locali di Cloud Datastore e altri servizi App Engine.

I servizi di sviluppo simulano il comportamento del servizio reale in locale per i test. Ad esempio, l'utilizzo del datastore mostrato in Scrittura di test di Cloud Datastore e Memcache consente di testare il codice del datastore senza effettuare richieste al datastore reale. Qualsiasi entità archiviata durante il test delle unità del datastore viene archiviata in locale e eliminata dopo l'esecuzione del test. Puoi eseguire piccoli test rapidi senza alcuna dipendenza dal datastore stesso.

Questo documento descrive come scrivere test delle unità su servizi App Engine locali utilizzando il pacchetto di test Go.

Pacchetto di test Go

Puoi automatizzare il download, la creazione e i test dei pacchetti Go utilizzando lo strumento goapp. goapp fa parte dell'SDK App Engine per Go.

La combinazione del comando goapp test e del pacchetto Go testing standard può essere utilizzata per eseguire test delle unità sul codice dell'applicazione. Per informazioni di base sui test con Go, consulta la sezione Test di Come scrivere codice di Go e il riferimento al pacchetto di test.

I test delle unità sono contenuti in file che terminano con il suffisso _test.go. Ad esempio, supponi di voler testare una funzione denominata composeNewsletter che restituisce un *mail.Message. Il seguente file newsletter_test.go mostra un semplice test per tale funzione:

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

Questo test verrà richiamato utilizzando il comando goapp test dall'interno della directory del pacchetto:

goapp test

Lo strumento goapp si trova nella directory principale dell'SDK di App Engine. Ti consigliamo di inserire questa directory nella variabile PATH del tuo sistema per semplificare l'esecuzione dei test.

Il pacchetto aetest

Molte chiamate di funzione ai servizi App Engine richiedono un context.Context come argomento. Il pacchetto appengine/aetest fornito con l'SDK consente di creare un context.Context fittizio per eseguire i test utilizzando i servizi forniti nell'ambiente di sviluppo.

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

La chiamata a aetest.NewContext avvierà dev_appserver.py in un sottoprocesso, che verrà utilizzato per gestire le chiamate API durante il test. Questo processo secondario verrà arrestato con la chiamata a done.

Per un maggiore controllo sull'istanza sottostante, puoi utilizzare invece aetest.NewInstance. Questo ti consente di creare più contesti e di associarli agli oggetti 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.
	// ...
}

Per ulteriori informazioni, consulta il riferimento al pacchetto aetest.

La scrittura di test di Cloud Datastore e Memcache

Testare il codice che utilizza il datastore o memcache è semplice dopo aver creato un elemento context.Context con il pacchetto aetest: nella chiamata di prova aetest.NewContext per creare un contesto da passare alla funzione sottoposta a test.

L'applicazione demo transaction nell'SDK presenta un esempio di strutturazione del codice per consentire testbilità e su come testare il codice che utilizza il 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)
	}
}

Questo test può essere eseguito utilizzando il comando goapp test:

goapp test ./demos/transaction

I test per memcache seguono lo stesso pattern: configura lo stato iniziale di memcache nel tuo test, esegui la funzione da testare e verifica che la funzione abbia eseguito query/modificate su memcache nel modo previsto.