Pruebas unitarias locales para Go

Las pruebas unitarias locales se ejecutan en tu entorno sin acceder a componentes remotos. App Engine proporciona utilidades de prueba que usan implementaciones locales de Cloud Datastore y otros servicios de App Engine.

Los servicios de desarrollo simulan el comportamiento del servicio real de forma local para realizar pruebas. Por ejemplo, el uso de Datastore que se muestra en Escribir pruebas de Cloud Datastore y Memcache te permite probar tu código de Datastore sin enviar ninguna solicitud al Datastore real. Cualquier entidad almacenada durante una prueba unitaria del almacén de datos se almacena de forma local y se elimina después de la prueba. Puedes realizar pruebas pequeñas y rápidas sin depender del propio almacén de datos.

En este documento se describe cómo escribir pruebas unitarias en servicios locales de App Engine con el paquete de pruebas de Go.

El paquete de pruebas de Go

Puedes automatizar la descarga, la compilación y la prueba de paquetes de Go con la herramienta goapp. goapp forma parte del SDK de App Engine para Go.

La combinación del comando goapp test y el paquete estándar Go testing se puede usar para ejecutar pruebas unitarias en el código de tu aplicación. Para obtener información general sobre las pruebas con Go, consulta la sección Pruebas del artículo Cómo escribir código de Go y la referencia del paquete de pruebas.

Las pruebas unitarias se encuentran en archivos que terminan con el sufijo _test.go. Por ejemplo, supongamos que quieres probar una función llamada composeNewsletter que devuelve un *mail.Message. El siguiente archivo newsletter_test.go muestra una prueba sencilla de esa función:

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

Esta prueba se invocaría con el comando goapp test desde el directorio del paquete:

goapp test

La herramienta goapp se encuentra en el directorio raíz del SDK de App Engine. Te recomendamos que incluyas este directorio en la variable PATH de tu sistema para que sea más fácil ejecutar pruebas.

Paquete aetest

Muchas llamadas a funciones de servicios de App Engine requieren un context.Context como argumento. El paquete appengine/aetest proporcionado con el SDK te permite crear un context.Context falso para ejecutar tus pruebas con los servicios proporcionados en el entorno de desarrollo.

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 llamada a aetest.NewContext iniciará dev_appserver.py en un subproceso, que se usará para atender las llamadas a la API durante la prueba. Este subproceso se cerrará con la llamada a done.

Si quieres tener más control sobre la instancia subyacente, puedes usar aetest.NewInstance en su lugar. De esta forma, puedes crear varios contextos y asociarlos a objetos 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.
	// ...
}

Consulta la referencia del paquete aetest para obtener más información.

Escribir pruebas de Cloud Datastore y memcache

Probar el código que usa el almacén de datos o la caché en memoria es sencillo una vez que creas un context.Context con el paquete aetest: en tu llamada de prueba, aetest.NewContext para crear un contexto que se pasará a la función que se está probando.

La aplicación de demostración transaction del SDK incluye un ejemplo de cómo estructurar el código para permitir que se pueda probar y cómo probar el código que usa el almacén de datos:

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

Esta prueba se puede ejecutar con el comando goapp test:

goapp test ./demos/transaction

Las pruebas de memcache siguen el mismo patrón: configura el estado inicial de memcache en tu prueba, ejecuta la función que se está probando y comprueba que la función ha consultado o modificado memcache de la forma esperada.