Testes de unidades locais para Go

Os testes unitários locais são executados no seu ambiente sem aceder a componentes remotos. O App Engine oferece utilitários de testes que usam implementações locais do Cloud Datastore e outros serviços do App Engine.

Os serviços de desenvolvimento simulam o comportamento do serviço real localmente para testes. Por exemplo, a utilização do arquivo de dados apresentada em Escrever testes do Cloud Datastore e do Memcache permite-lhe testar o seu código do arquivo de dados sem fazer pedidos ao arquivo de dados real. Qualquer entidade armazenada durante um teste de unidade da base de dados é armazenada localmente e é eliminada após a execução do teste. Pode executar testes pequenos e rápidos sem qualquer dependência do próprio armazeno de dados.

Este documento descreve como escrever testes unitários em serviços locais do App Engine usando o pacote de testes Go.

O pacote Go Testing

Pode automatizar a transferência, a criação e o teste de pacotes Go através da ferramenta goapp. goapp faz parte do SDK do App Engine para Go.

A combinação do comando goapp test e do pacote Go padrão testing pode ser usada para executar testes unitários no código da sua aplicação. Para obter informações gerais sobre os testes com Go, consulte a secção Testes do artigo Como escrever código Go e a referência do pacote de testes.

Os testes unitários estão contidos em ficheiros que terminam com o sufixo _test.go. Por exemplo, suponha que quer testar uma função denominada composeNewsletter que devolve um *mail.Message. O ficheiro newsletter_test.go seguinte mostra um teste simples para essa função:

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

Este teste seria invocado através do comando goapp test no diretório do pacote:

goapp test

A ferramenta goapp encontra-se no diretório raiz do App Engine SDK. Recomendamos que coloque este diretório na variável PATH do seu sistema para simplificar a execução de testes.

O pacote aetest

Muitas chamadas de funções para serviços do App Engine requerem um context.Context como argumento. O pacote appengine/aetest fornecido com o SDK permite-lhe criar um context.Context falso para executar os seus testes através dos serviços fornecidos no ambiente de desenvolvimento.

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

A chamada para aetest.NewContext vai começar dev_appserver.py num subprocesso, que vai ser usado para atender chamadas API durante o teste. Este subprocesso vai ser encerrado com a chamada para done.

Para ter mais controlo sobre a instância subjacente, pode usar aetest.NewInstance em alternativa. Isto permite-lhe criar vários contextos e associá-los 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.
	// ...
}

Leia a referência do pacote aetest para mais informações.

Escrever testes do Cloud Datastore e do memcache

Testar código que usa o datastore ou o memcache é simples depois de criar um context.Context com o pacote aetest: na sua chamada de teste aetest.NewContext para criar um contexto a transmitir à função em teste.

A aplicação de demonstração transaction no SDK tem um exemplo de estruturação do código para permitir a capacidade de teste e como testar o código que usa o armazenamento de dados:

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

Este teste pode ser executado através do comando goapp test:

goapp test ./demos/transaction

Os testes para memcache seguem o mesmo padrão: configure o estado inicial do memcache no seu teste, execute a função que está a ser testada e verifique se a função consultou/modificou o memcache da forma esperada.