Prueba de unidades locales para Go

Las pruebas de unidades locales se ejecutan dentro del entorno sin acceder a los 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 a nivel local para las pruebas. Por ejemplo, el uso del almacén de datos que se muestra en Cómo escribir pruebas de Cloud Datastore y Memcache te permite probar el código del almacén de datos sin enviar solicitudes al almacén de datos real. Cualquier entidad almacenada durante una prueba de unidades del almacén de datos se almacena a nivel local y se borra después de ejecutar la prueba. Puedes ejecutar pruebas pequeñas y rápidas sin depender del almacén de datos.

En este documento, se detalla cómo escribir pruebas de unidades en los 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 los paquetes de Go mediante la herramienta de goapp. goapp es parte del SDK de App Engine para Go.

La combinación del comando goapp test y el paquete de testing estándar de Go se puede usar para ejecutar pruebas de unidades con el código de la aplicación. Para obtener información adicional sobre la prueba con Go, consulta la sección de Testing (Pruebas) en How to Write Go Code (Cómo escribir código de Go) y la referencia del paquete de pruebas.

Las pruebas de unidades se encuentran en los archivos que terminan con el sufijo _test.go. Por ejemplo, supongamos que deseas probar una función llamada composeNewsletter, que muestra un *mail.Message. En el siguiente archivo newsletter_test.go, se muestra una prueba simple para 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á mediante el comando goapp test desde el directorio del paquete:

goapp test

La herramienta de goapp se encuentra en el directorio raíz del SDK de App Engine. Recomendamos poner este directorio en la variable PATH de tu sistema a fin de que la ejecución de las pruebas sea más sencilla.

El paquete aetest

Muchas llamadas a función para los 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 las pruebas mediante los servicios que se proporcionan 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 proceso se cerrará con la llamada a done.

Para obtener más control sobre la instancia subyacente, puedes usar aetest.NewInstance en su lugar. Esto te brinda la capacidad de crear varios contextos y de asociarlos con 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.
	// ...
}

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

Escribe pruebas de Cloud Datastore y Memcache

Probar el código que usa Datastore o Memcache es sencillo una vez que creas un context.Context con el paquete aetest: en tu prueba, llama a aetest.NewContext para crear un contexto a fin de pasar a la función de la prueba.

La aplicación de demostración transaction en el SDK tiene un ejemplo de estructura del código para permitir la capacidad de prueba y de 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 para Memcache siguen el mismo patrón: configura el estado inicial de Memcache en la prueba, ejecuta la función que se probará y verifica que la función haya consultado o modificado a Memcache de la forma que esperas.